#include "StdAfx.h"
#include "I3DEngine.h"
#include "IIndexedMesh.h"
#include "CGFContent.h"
#include "GeomQuery.h"
#include "RenderMesh.h"
#include "RenderObjectDefines.h"
#include <SpuUtils.h>
#if !defined(__SPU__)
#include "PostProcess/PostEffects.h"
#endif
#include <IJobManager_JobDelegator.h>

#define DELETE_SUBSET_MESHES_AFTER_NOTUSED_FRAMES 30
#define RENDERMESH_ASYNC_MEMCPY_THRESHOLD (1<<10)

namespace 
{ 
class CConditonalDevManLock
{
	int m_Active; 
public: 
	CConditonalDevManLock(volatile int active) : m_Active((int)active)
	{ 
		if (m_Active) 
			gRenDev->m_DevBufMan.LockDevMan(); 
	}

	~CConditonalDevManLock() 
	{ 
		if (m_Active) 
			gRenDev->m_DevBufMan.UnlockDevMan(); 
	}
};

class CConditionalLock
{
	CryCriticalSection &_lock; 
	bool _bActive; 
public: 
	CConditionalLock(CryCriticalSection& lock, bool bActive) 
		: _lock(lock), _bActive(bActive)
	{ 
		if (_bActive) 
			_lock.Lock(); 
	}

	~CConditionalLock() 
	{ 
		if (_bActive) 
			_lock.Unlock(); 
	}
};

static inline void RelinkTail(SIntrusiveList& instance, SIntrusiveList& list) 
{
	CConditionalLock lock(CRenderMesh::m_sLinkLock, !gRenDev->m_pRT->IsRenderThread());
	instance.relink_tail(list);
}

struct SMeshPool 
{
	CryCriticalSection m_MeshPoolCS; 
	IGeneralMemoryHeap *m_MeshDataPool;
	void* m_MeshDataMemory;
	SMeshPoolStatistics m_MeshDataPoolStats; 
	SMeshPool()
		: m_MeshPoolCS()
		, m_MeshDataPool()
		, m_MeshDataMemory()
		, m_MeshDataPoolStats()
	{}
};
static SMeshPool s_MeshPool;

//////////////////////////////////////////////////////////////////////////
static void* AllocateMeshData(size_t nSize, size_t nAlign = 16, bool bFlush = false)
{
	nSize = (nSize + (nAlign-1)) & ~(nAlign-1);

	if (s_MeshPool.m_MeshDataPool && s_MeshPool.m_MeshDataPoolStats.nPoolSize > nSize)
	{
	try_again:
		s_MeshPool.m_MeshPoolCS.Lock();
		void* ptr = s_MeshPool.m_MeshDataPool->Memalign(nAlign, nSize, "RENDERMESH_POOL");
		if (ptr) 
		{ 
			s_MeshPool.m_MeshDataPoolStats.nPoolInUse += s_MeshPool.m_MeshDataPool->UsableSize(ptr); 
			s_MeshPool.m_MeshDataPoolStats.nPoolInUsePeak = 
				std::max(
					s_MeshPool.m_MeshDataPoolStats.nPoolInUsePeak, 
					s_MeshPool.m_MeshDataPoolStats.nPoolInUse);
			s_MeshPool.m_MeshPoolCS.Unlock();
			return ptr;
		}
		else
		{
			s_MeshPool.m_MeshPoolCS.Unlock();
			// Clean up the stale mesh temporary data - and do it from the main thread. 
			if (gRenDev->m_pRT->IsMainThread() && CRenderMesh::ClearStaleMemory(false,gRenDev->m_RP.m_nFillThreadID)) 
			{
				goto try_again; 
			}
			else if (gRenDev->m_pRT->IsRenderThread() && CRenderMesh::ClearStaleMemory(false,gRenDev->m_RP.m_nProcessThreadID)) 
			{
				goto try_again; 
			}
		}
		s_MeshPool.m_MeshPoolCS.Lock();
		s_MeshPool.m_MeshDataPoolStats.nFallbacks += nSize; 
		s_MeshPool.m_MeshPoolCS.Unlock();
	}
	return CryModuleMemalign(nSize, nAlign);
}

//////////////////////////////////////////////////////////////////////////
static void FreeMeshData(void* ptr)
{
	if (ptr == NULL) 
		return;
	{
		AUTO_LOCK(s_MeshPool.m_MeshPoolCS);
		size_t nSize = 0u; 
		if (s_MeshPool.m_MeshDataPool && (nSize=s_MeshPool.m_MeshDataPool->Free(ptr)) > 0)
		{
			s_MeshPool.m_MeshDataPoolStats.nPoolInUse -= 
				(nSize < s_MeshPool.m_MeshDataPoolStats.nPoolInUse) ? nSize : s_MeshPool.m_MeshDataPoolStats.nPoolInUse; 
			return;
		}
	}

	CryModuleMemalignFree(ptr);

}

template<typename Type> 
static Type* AllocateMeshData(size_t nCount = 1)
{
	void* pStorage = AllocateMeshData(sizeof(Type) * nCount, std::max((size_t)TARGET_DEFAULT_ALIGN,__alignof(Type)));
	if (!pStorage) 
		return NULL;
	Type* pTypeArray = reinterpret_cast<Type*>(pStorage); 
	for (size_t i=0; i<nCount; ++i) 
		new (pTypeArray + i) Type;
	return pTypeArray;
}
  

static bool IsPoolAllocated(void *ptr) 
{
	if (s_MeshPool.m_MeshDataPool && s_MeshPool.m_MeshDataPool->IsInAddressRange(ptr))
		return true;
	return false;
}

static bool InitializePool() 
{
	if (gRenDev->CV_r_meshpoolsize > 0) 
	{ 
		if (s_MeshPool.m_MeshDataPool || s_MeshPool.m_MeshDataMemory)
		{
			CryFatalError("render meshpool already initialized");
			return false; 
		}
		size_t poolSize = static_cast<size_t>(gRenDev->CV_r_meshpoolsize)*1024U; 
		s_MeshPool.m_MeshDataMemory = CryModuleMemalign(poolSize, 128u);
		if (!s_MeshPool.m_MeshDataMemory) 
		{
			CryFatalError("could not allocate render meshpool");
			return false; 
		}

		// Initialize the actual pool
		s_MeshPool.m_MeshDataPool = gEnv->pSystem->GetIMemoryManager()->CreateGeneralMemoryHeap(
			s_MeshPool.m_MeshDataMemory, poolSize,  "RENDERMESH_POOL");
		s_MeshPool.m_MeshDataPoolStats.nPoolSize = poolSize; 
	} 
	return true;
}

static void ShutdownPool()
{
	if (s_MeshPool.m_MeshDataPool)
	{
		s_MeshPool.m_MeshDataPool->Release(); s_MeshPool.m_MeshDataPool=NULL;
	}
	if (s_MeshPool.m_MeshDataMemory)
	{
		CryModuleMemalignFree(s_MeshPool.m_MeshDataMemory);
		s_MeshPool.m_MeshDataMemory=NULL; 
	}
}
} 

#define alignup(alignment, value) 	((((uintptr_t)(value))+((alignment)-1))&(~((uintptr_t)(alignment)-1)))
#define alignup16(value)	alignup(16, value)


DECLARE_JOB("RenderMesh_SetMesh", TRenderMesh_SetMeshJob, CRenderMesh::SetMesh_IntImpl );

namespace
{
	inline uint32 GetCurrentRenderFrameID()
	{
		ASSERT_IS_MAIN_THREAD(gRenDev->m_pRT);
		return gRenDev->m_RP.m_TI[gRenDev->m_RP.m_nFillThreadID].m_nFrameUpdateID;
	};
}

#if defined(USE_VBIB_PUSH_DOWN)



#if !defined(__SPU__)
static inline bool VidMemPushDown(void* pDst, const void* pSrc, size_t nSize, void* pDst1=NULL, const void* pSrc1=NULL, size_t nSize1=0, int cachePosStride = -1, void* pFP16Dst=NULL, uint32 nVerts=0)
{

}
#endif
#define ASSERT_LOCK
static std::vector<CRenderMesh*> g_MeshCleanupVec;//for cleanup of meshes itself
#else
#define ASSERT_LOCK assert((m_nVerts == 0) || pData)
#endif

#if !defined(_RELEASE)
ILINE void CheckVideoBufferAccessViolation(CRenderMesh& mesh)
{
	//LogWarning("CRenderMesh::LockVB: accessing video buffer for cgf=%s",mesh.GetSourceName());
}
#define MESSAGE_VIDEO_BUFFER_ACC_ATTEMPT CheckVideoBufferAccessViolation(*this)
#else
#define MESSAGE_VIDEO_BUFFER_ACC_ATTEMPT
#endif

#ifndef __SPU__
CryCriticalSection CRenderMesh::m_sLinkLock;

// General vertex stream stride
int32 CRenderMesh::m_cSizeVF[eVF_Max] = 
{
	0,
	sizeof(SVF_P3F_C4B_T2F),
	sizeof(SVF_P3S_C4B_T2S),
	sizeof(SVF_P3S_N4B_C4B_T2S),
	sizeof(SVF_P3F_C4B_T4B_N3F2),
	sizeof(SVF_TP3F_C4B_T2F),
	sizeof(SVF_TP3F_T2F_T3F),
	sizeof(SVF_P3F_T3F),
	sizeof(SVF_P3F_T2F_T3F),
	sizeof(SVF_T2F),
	sizeof(SVF_W4B_I4B),
	sizeof(SVF_C4B_C4B),
	sizeof(SVF_P3F_P3F_I4B),
	sizeof(SVF_P3F),
	sizeof(SVF_C4B_T2S),
	sizeof(SVF_P2S_N4B_C4B_T1F),
	sizeof(SVF_P3F_C4B_T2S)
};
// Additional streams stride
int32 CRenderMesh::m_cSizeStream[VSF_NUM] = 
{
	-1,
	sizeof(SPipTangents),        // VSF_TANGENTS
	sizeof(SQTangents),          // VSF_QTANGENTS
	sizeof(SVF_W4B_I4B),         // VSF_HWSKIN_INFO
	sizeof(SVF_P3F),             // VSF_HWSKIN_MORPHTARGET_INFO
	# if ENABLE_SHAPEDEFORMATION_SUPPORT
	sizeof(SVF_P3F_P3F_I4B),     // VSF_HWSKIN_SHAPEDEFORM_INFO
	# endif
	# if ENABLE_NORMALSTREAM_SUPPORT
	sizeof(Vec3),                // VSF_NORMALS
	#endif
};

#define OOFS(t, x) (int)(INT_PTR)&(((t*)0)->x) 

SBufInfoTable CRenderMesh::m_cBufInfoTable[eVF_Max] = 
{
	{0},
	{  //eVF_P3F_C4B_T2F
		OOFS(SVF_P3F_C4B_T2F, st),
		OOFS(SVF_P3F_C4B_T2F, color.dcolor)
	},
	{  //eVF_P3S_C4B_T2S
		OOFS(SVF_P3S_C4B_T2S, st),
		OOFS(SVF_P3S_C4B_T2S, color.dcolor)
	},
	{  //eVF_P3F_N4B_C4B_T2F
		OOFS(SVF_P3S_N4B_C4B_T2S, st),
		OOFS(SVF_P3S_N4B_C4B_T2S, color.dcolor),
	},

	{  // eVF_P3F_C4B_T4B_N3F2
		0,
		OOFS(SVF_P3F_C4B_T4B_N3F2, color.dcolor),
	},
	{  // eVF_TP3F_C4B_T2F
		OOFS(SVF_TP3F_C4B_T2F, st[0]),
		OOFS(SVF_TP3F_C4B_T2F, color.dcolor),
	},
	{  // eVF_TP3F_T2F_T3F
		OOFS(SVF_TP3F_T2F_T3F, st0[0]),
	},
	{  // eVF_P3F_T3F
		OOFS(SVF_P3F_T3F, st[0]),
	},
	{  // eVF_P3F_T2F_T3F
		OOFS(SVF_P3F_T2F_T3F, st0[0]),
	},
	{ 0 }, // eVF_T2F
	{ 0 }, // eVF_W4B_I4B_P3F
	{ 0 }, // eVF_C4B_C4B
	{ 0 }, // eVF_P3F_P3F_I4B
	{ 0 }, // eVF_P3F_MT
	{			 // eVF_C4B_T2S
		OOFS(SVF_C4B_T2S, st),	
		OOFS(SVF_C4B_T2S, color.dcolor),	
	},
	{   // eVF_P2S_N4B_C4B_T1F
		0,	
		OOFS(SVF_P2S_N4B_C4B_T1F, color.dcolor),	
	},
	{   // eVF_P3F_C4B_T2S
		OOFS(SVF_P3F_C4B_T2S, st),
		OOFS(SVF_P3F_C4B_T2S, color.dcolor)
	}
};

#undef OOFS

SIntrusiveList CRenderMesh::m_MeshList;
SIntrusiveList CRenderMesh::m_MeshGarbageList[MAX_RELEASED_MESH_FRAMES];
SIntrusiveList CRenderMesh::m_MeshDirtyList[2];
SIntrusiveList CRenderMesh::m_MeshModifiedList[2];

int CRenderMesh::Release()
{
	long refCnt = CryInterlockedDecrement(&m_nRefCounter);
# if !defined(_RELEASE)
	if (refCnt < 0)
	{
		CryLogAlways("CRenderMesh::Release() called so many times on rendermesh that refcount became negative"); 
		if (CRenderer::CV_r_BreakOnError)
			__debugbreak();
	}
# endif 

	if (refCnt == 0) 
	{
		AUTO_LOCK(m_sLinkLock);
#   if !defined(_RELEASE)
		if (m_nFlags & FRM_RELEASED) 
		{
			CryLogAlways("CRenderMesh::Release() mesh already in the garbage list (double delete pending)"); 
			if (CRenderer::CV_r_BreakOnError)
				__debugbreak();
		}
#   endif
		gRenDev->m_p3DEngineCommon.m_RainOccluders.m_bForceUpdate = true;
		m_nFlags |= FRM_RELEASED;
		int nFrame = gRenDev->GetFrameID(false);
		SIntrusiveList* garbage = &CRenderMesh::m_MeshGarbageList[nFrame & (MAX_RELEASED_MESH_FRAMES-1)];
		m_Chain.relink_tail(garbage);
	}

	return refCnt;
}

CRenderMesh::CRenderMesh()
{	
#if defined(USE_VBIB_PUSH_DOWN)
	m_VBIBFramePushID = 0;
#endif

	m_nLastRenderFrameID = 0;
	m_nLastSubsetGCRenderFrameID = 0;
	m_nThreadAccessCounter = 0;
	for (size_t i=0; i<2; ++i)
		m_asyncUpdateState[i] = m_asyncUpdateStateCounter[i] = 0;
	m_pTesselatedRenderMesh = NULL;

# if !defined(_RELEASE) && defined(RM_CATCH_EXCESSIVE_LOCKS)
	m_lockTime = 0.f; 
# endif 
}

CRenderMesh::CRenderMesh (const char *szType, const char *szSourceName, bool bLock)
{	
	IF(bLock, 0)//when called from another thread like the Streaming AsyncCallbackCGF, we need to lock it
	{
		LockForThreadAccess();
	}

	m_nRefCounter = 0;
	m_nLastRenderFrameID = 0;
	m_nLastSubsetGCRenderFrameID = 0;

	m_sType = szType;
	m_sSource = szSourceName;

	m_vBoxMin = m_vBoxMax = Vec3(0,0,0); //used for hw occlusion test
	m_nVerts = 0;
	m_nInds = 0;
	m_eVF = eVF_P3F_C4B_T2F;
	m_pVertexContainer = NULL;

	{
		AUTO_LOCK(m_sLinkLock);
		m_Chain.link_tail(&m_MeshList);
	}

	m_pCustomData = NULL;
	m_pChunksSkinned = NULL;
	m_nPrimetiveType = eptTriangleList;

	//  m_nFrameRender = 0;
	//m_nFrameUpdate = 0;
	m_arrVtxMap = NULL;
	m_nClientTextureBindID = 0;
	#ifdef RENDER_MESH_TRIANGLE_HASH_MAP_SUPPORT
	m_pTrisMap = NULL;
	#endif

	#ifdef KEEP_POSITIONS_INDICES
	m_pSysPosData = NULL;
	#endif

#ifdef FP16_MESH
	m_pCachePos = NULL;        
	m_nFrameRequestCachePos = 0;
	m_nFlagsCachePos = 0;
#endif

	_SetRenderMeshType(eRMT_Static);

	m_nFlags = 0;
	m_nLod = 0;

#if defined(USE_VBIB_PUSH_DOWN)
	m_VBIBFramePushID = 0;
#endif

	m_nThreadAccessCounter = 0;
	m_pTesselatedRenderMesh = NULL;
	for (size_t i=0; i<2; ++i)
	{
		m_asyncUpdateState[i] = m_asyncUpdateStateCounter[i] = 0;
	}
}

void CRenderMesh::Cleanup()
{
	FreeDeviceBuffers(false);
	FreeSystemBuffers();

	m_meshSubSetIndices.clear();

	if (m_pVertexContainer)
	{
		m_pVertexContainer->m_lstVertexContainerUsers.Delete(this);
		m_pVertexContainer = NULL;
	}

	for(int i=0; i<m_lstVertexContainerUsers.Count(); i++)
	{
		if (m_lstVertexContainerUsers[i]->GetVertexContainer() == this)
			m_lstVertexContainerUsers[i]->m_pVertexContainer = NULL;
	}
	m_lstVertexContainerUsers.Clear();

	if (m_pChunksSkinned)
	{
		ReleaseRenderChunks(m_pChunksSkinned);
		SAFE_DELETE(m_pChunksSkinned);
	}

	ReleaseRenderChunks(&m_ChunksSubObjects);
	ReleaseRenderChunks(&m_Chunks);
	ReleaseRenderChunks(&m_ChunksMerged);

	m_ChunksSubObjects.Clear();
	m_Chunks.Clear();
	m_ChunksMerged.Clear();

#ifdef RENDER_MESH_TRIANGLE_HASH_MAP_SUPPORT
	SAFE_DELETE(m_pTrisMap);
#endif

	if(m_pTesselatedRenderMesh && m_pTesselatedRenderMesh != this)
	{
		gRenDev->DeleteRenderMesh(m_pTesselatedRenderMesh);
	}

	for (size_t i=0; i<2; ++i)
	{
		m_asyncUpdateState[i] = m_asyncUpdateStateCounter[i] = 0;
	}
}

//////////////////////////////////////////////////////////////////////////
CRenderMesh::~CRenderMesh()
{	
	// make sure to stop and delete all mesh subset indice tasks
	ASSERT_IS_RENDER_THREAD(gRenDev->m_pRT); 

	for (int i=0; i<2; SyncAsyncUpdate(i++));
	CleanupMeshSubSetIndicesTasks();	
	assert(m_nThreadAccessCounter == 0);

	{ 
		AUTO_LOCK(m_sLinkLock);
		for (int i=0; i<2; ++i) 
		{
			m_Dirty[i].erase(), m_Modified[i].erase();
		}
		m_Chain.erase();
	}
	
	Cleanup();
}
#endif//__SPU__

void CRenderMesh::ReleaseRenderChunks(PodArray<CRenderChunk> *pChunks)
{
	if (pChunks)
	{
		for (size_t i=0, c=pChunks->size(); i!=c; ++i)
		{
			CRenderChunk &rChunk = pChunks->GetAt(i);

			if (rChunk.pRE)
			{
				CREMeshImpl* pRE = static_cast<CREMeshImpl*>(rChunk.pRE);
				pRE->Release(false);
				pRE->m_pRenderMesh = NULL; 
				rChunk.pRE = 0;
			}
#ifndef _DEBUG
			rChunk.m_arrChunkBoneIDs.Free();
#endif
		}
	}
}

void *CRenderMesh::LockVB(int nStream, uint32 nFlags, int nOffset, int nVerts, int *nStride, bool prefetchIB, bool inplaceCachePos)
{
	MEMSTAT_CONTEXT(EMemStatContextTypes::MSC_RenderMeshType, 0, this->GetTypeName());
	MEMSTAT_CONTEXT(EMemStatContextTypes::MSC_RenderMesh, 0, this->GetSourceName());

# if !defined(_RELEASE)
	if (!m_nThreadAccessCounter) 
	{
		CryLogAlways("rendermesh must be locked via LockForThreadAccess() before LockIB/VB is called");
		if (CRenderer::CV_r_BreakOnError)
		{
			__debugbreak();
		}
	}
#endif

	const int threadId = gRenDev->m_RP.m_nFillThreadID; 

	SMeshStream& MS = m_VBStream[nStream];
	#if defined(USE_VBIB_PUSH_DOWN) && !defined(__SPU__)
	AUTO_LOCK(m_sResLock);//need lock as resource must not be updated concurrently
	m_VBIBFramePushID = gRenDev->m_RP.m_TI[gRenDev->m_RP.m_nFillThreadID].m_nFrameUpdateID;
	if (nFlags == FSL_SYSTEM_CREATE || nFlags == FSL_SYSTEM_UPDATE)
		MS.m_nLockFlags &= ~FSL_VBIBPUSHDOWN;
	#endif

	assert(nVerts <= (int)m_nVerts);
	if (nVerts > (int)m_nVerts)
		nVerts = m_nVerts;
	if (nStride)
		*nStride = GetStreamStride(nStream);

	m_nFlags |= FRM_READYTOUPLOAD;

	byte *pD;
	#if !defined(__SPU__) // SPUs only currently read, so no need for this code
	int nFrame = gRenDev->m_RP.m_TI[gRenDev->m_RP.m_nFillThreadID].m_nFrameUpdateID;

	if (nFlags == FSL_SYSTEM_CREATE)
	{
		lSysCreate:
			RelinkTail(m_Modified[threadId], m_MeshModifiedList[threadId]); 

		if (!MS.m_pUpdateData)
		{
			uint32 nSize = GetStreamSize(nStream);
			pD = reinterpret_cast<byte*>(AllocateMeshData(nSize));
			if (!pD) return NULL; 
			MS.m_pUpdateData = pD;
		}
		else
		{
			pD = (byte *)MS.m_pUpdateData;
		}

		//MS.m_nFrameRequest = nFrame;
		MS.m_nLockFlags = (FSL_SYSTEM_CREATE | (MS.m_nLockFlags & FSL_LOCKED));
		return &pD[nOffset];
	}
	else if (nFlags == FSL_SYSTEM_UPDATE)
	{
lSysUpdate:
		RelinkTail(m_Modified[threadId], m_MeshModifiedList[threadId]); 
		if (!MS.m_pUpdateData)
		{
			MESSAGE_VIDEO_BUFFER_ACC_ATTEMPT;
			CopyVBToSystemForUpdate(nStream);
		}
		assert(nStream || MS.m_pUpdateData);
		if (!MS.m_pUpdateData)
			return NULL;
		//MS.m_nFrameRequest = nFrame;
		pD = (byte *)MS.m_pUpdateData;
		MS.m_nLockFlags = (nFlags | (MS.m_nLockFlags & FSL_LOCKED));
		return &pD[nOffset];
	}
	else
#endif//__SPU__
		if (nFlags == FSL_READ)
		{
			RelinkTail(m_Dirty[threadId], m_MeshDirtyList[threadId]); 
			if (MS.m_pUpdateData)
			{
				pD = (byte *)MS.m_pUpdateData;
				return &pD[nOffset];
			}
#if !defined(__SPU__)
#if defined(USE_VBIB_PUSH_DOWN) && !defined(XENON)
	else
	{
		if(MS.m_DevBuf.m_nID>=0)
		{
			int devOffset;
			uint32 nSize;
			D3DVertexBuffer *pVB;
			{
				CConditonalDevManLock lock(m_nThreadAccessCounter);
				SDevBuffer *pDev = gRenDev->m_DevBufMan.GetDevVB(MS.m_DevBuf.m_nID);
				pVB = pDev->m_D3DBuf.m_pVB;
				nSize = pDev->m_nStagedSize;
				devOffset = pDev->m_nDevOffset;
			}
			if(nSize && pVB)
			{
				pD = reinterpret_cast<byte*>(AllocateMeshData(nSize,TARGET_DEFAULT_ALIGN));
				if (pD) 
				{
					pVB->Sync();//sync resource upload
					uint8*const pDevBuf = (uint8*)pVB->RawData()+devOffset;
					void* pDstIB = NULL;
					const void* pSrcIB = NULL;
					size_t nSizeIB = 0;
					if(prefetchIB)
					{
						if(m_IBStream.m_DevBuf.m_nID>=0 && m_IBStream.m_pUpdateData == NULL)
						{
							int devOffsetIB;
							uint32 sizeIB;
							D3DIndexBuffer *pIB;
							{
								CConditonalDevManLock lock(m_nThreadAccessCounter);
								SDevBuffer *pDevIB = gRenDev->m_DevBufMan.GetDevIB(m_IBStream.m_DevBuf.m_nID);
								pIB = pDevIB->m_D3DBuf.m_pIB;
								sizeIB = pDevIB->m_nStagedSize;
								devOffsetIB = pDevIB->m_nDevOffset;
							}
							if(sizeIB && pIB)
							{
								pDstIB = reinterpret_cast<byte*>(AllocateMeshData(sizeIB,TARGET_DEFAULT_ALIGN));
								if (pDstIB)
								{
									pSrcIB = (uint8*)pIB->RawData()+devOffsetIB;
									pIB->Sync();//sync resource upload
									nSizeIB = sizeIB;
								}
							}
						}
					}
					bool doInplaceCachePos = false;
					if(inplaceCachePos)
						doInplaceCachePos = PrepareCachePos();//allocates mesh if non existing
					if(!VidMemPushDown(pD,pDevBuf,nSize,pDstIB,pSrcIB,nSizeIB,doInplaceCachePos?*nStride:-1,doInplaceCachePos?m_pCachePos:NULL,m_nVerts))
					{
						FreeMeshData(pD);
						if(doInplaceCachePos && m_pCachePos)
						{
							FreeMeshData(m_pCachePos);
							m_pCachePos = NULL;
						}
						if(pDstIB)
							FreeMeshData(pDstIB);
						return NULL;
					}
					if(pDstIB)
					{
						m_IBStream.m_nLockFlags |= FSL_VBIBPUSHDOWN;
						m_IBStream.m_pUpdateData = pDstIB;
					}
					MS.m_nLockFlags |= FSL_VBIBPUSHDOWN;
					MS.m_pUpdateData = pD;
					return &pD[nOffset];
				}
				return NULL; 
			}
		}
	}
#endif
		nFlags = FSL_READ | FSL_VIDEO;
#endif//__SPU__
	}

#if !defined(__SPU__)
	if (nFlags == (FSL_READ | FSL_VIDEO))
	{
		RelinkTail(m_Dirty[threadId], m_MeshDirtyList[threadId]); 
		if (gRenDev->m_pRT && gRenDev->m_pRT->IsMultithreaded())
		{
			// Always use system copy in MT mode
			goto lSysUpdate;
		}
		else
		{
			int nVB = MS.m_DevBuf.m_nID;
			if (nVB < 0)
			{
				return NULL;
			}
			// Try to lock device buffer in single-threaded mode
			if (!MS.m_pLockedData)
			{
				MESSAGE_VIDEO_BUFFER_ACC_ATTEMPT;
				MS.m_pLockedData = gRenDev->m_DevBufMan.LockVB(nVB, 0, 0, FSL_READ);
				MS.m_nLockFlags |= FSL_LOCKED;
			}

			if (MS.m_pLockedData)
			{
#if defined(USE_VBIB_PUSH_DOWN) && !defined(XENON)
				if(MS.m_DevBuf.m_nID>=0)
				{
					uint32 nSize;
					{
						CConditonalDevManLock lock(m_nThreadAccessCounter);
						SDevBuffer *pDev = gRenDev->m_DevBufMan.GetDevVB(MS.m_DevBuf.m_nID);
						nSize = pDev->m_nStagedSize;
					}
					if(nSize)
					{
						pD = reinterpret_cast<byte*>(AllocateMeshData(nSize,TARGET_DEFAULT_ALIGN));
						if (pD)
						{
							if(!VidMemPushDown(pD,MS.m_pLockedData,nSize))
							{
								FreeMeshData(pD);
								return NULL;
							}
							MS.m_nLockFlags |= FSL_VBIBPUSHDOWN;
							MS.m_pUpdateData = pD;
							return &pD[nOffset];
						}
						return NULL; 
					}
				}
#endif
				pD = (byte *)MS.m_pLockedData;
				return &pD[nOffset];
			}
		}
	}

	if (nFlags == (FSL_VIDEO_CREATE))
	{
		// Only xbox and ps3 support direct uploading to vram, but as the data is not 
		// double buffered in the non-dynamic case, only creation is supported 
		// DX11 has to use the deferred context to call map, which is not threadsafe 
		// DX9 can experience huge stalls if resources are used while rendering is performed

		int nVB = -1;
#   if BUFFER_ENABLE_DIRECT_ACCESS
		nVB = MS.m_DevBuf.m_nID;
		if (nVB >= 0 && (MS.m_nFrameCreate != nFrame || MS.m_nElements != m_nVerts))
#   endif 
		goto lSysCreate;

		RelinkTail(m_Modified[threadId], m_MeshModifiedList[threadId]); 
		if (nVB < 0 && !CreateVidVertices(m_nVerts, m_eVF, nStream))
			return NULL;

		nVB = MS.m_DevBuf.m_nID;
		if (!MS.m_pLockedData)
		{
			MESSAGE_VIDEO_BUFFER_ACC_ATTEMPT;
			if ((MS.m_pLockedData = gRenDev->m_DevBufMan.LockVB(nVB, 0, 0, FSL_DIRECT|FSL_WRITE|FSL_DISCARD)) == NULL)
				return NULL;
		}
		pD = (byte *)MS.m_pLockedData;
		return &pD[nOffset];		
	}

	if (nFlags == (FSL_VIDEO_UPDATE))
	{
		// This code path will be enabled for xbox and ps3 as soon as dynamic meshes have been restored
		goto lSysUpdate;
	}
	#endif//__SPU__
	return NULL;
}

uint16 *CRenderMesh::LockIB(uint32 nFlags, int nOffset, int nInds)
{
	byte *pD;
# if !defined(_RELEASE)
	if (!m_nThreadAccessCounter) 
	{
		CryLogAlways("rendermesh must be locked via LockForThreadAccess() before LockIB/VB is called");
		if (CRenderer::CV_r_BreakOnError)
			__debugbreak();
	}
#endif
	const int threadId = gRenDev->m_RP.m_nFillThreadID; 

#ifndef __SPU__
	int nFrame = gRenDev->m_RP.m_TI[gRenDev->m_RP.m_nFillThreadID].m_nFrameUpdateID;

	#if defined(USE_VBIB_PUSH_DOWN)
	AUTO_LOCK(m_sResLock);//need lock as resource must not be updated concurrently
	m_VBIBFramePushID = gRenDev->m_RP.m_TI[gRenDev->m_RP.m_nFillThreadID].m_nFrameUpdateID;
	if (nFlags == FSL_SYSTEM_CREATE || nFlags == FSL_SYSTEM_UPDATE)
		m_IBStream.m_nLockFlags &= ~FSL_VBIBPUSHDOWN;
	#endif
	m_nFlags |= FRM_READYTOUPLOAD;

	assert(nInds <= (int)m_nInds);
	if (nFlags == FSL_SYSTEM_CREATE)
	{
lSysCreate:
		RelinkTail(m_Modified[threadId], m_MeshModifiedList[threadId]); 
		if (!m_IBStream.m_pUpdateData)
		{
			uint32 nSize = m_nInds * sizeof(uint16);
			pD = reinterpret_cast<byte*>(AllocateMeshData(nSize));
			if (!pD) return NULL;
			m_IBStream.m_pUpdateData = (uint16 *)pD;
		}
		else
		{
			pD = (byte *)m_IBStream.m_pUpdateData;
		}

		//m_IBStream.m_nFrameRequest = nFrame;
		m_IBStream.m_nLockFlags = (nFlags | (m_IBStream.m_nLockFlags & FSL_LOCKED));
		return (uint16 *)&pD[nOffset];
	}
	else if (nFlags == FSL_SYSTEM_UPDATE)
	{
lSysUpdate:
		RelinkTail(m_Modified[threadId], m_MeshModifiedList[threadId]); 
		if (!m_IBStream.m_pUpdateData)
		{
			MESSAGE_VIDEO_BUFFER_ACC_ATTEMPT;
			CopyIBToSystemForUpdate();
		}
		assert(m_IBStream.m_pUpdateData);
		if (!m_IBStream.m_pUpdateData)
			return NULL;
		//m_IBStream.m_nFrameRequest = nFrame;
		pD = (byte *)m_IBStream.m_pUpdateData;
		m_IBStream.m_nLockFlags = (nFlags | (m_IBStream.m_nLockFlags & FSL_LOCKED));
		return (uint16 *)&pD[nOffset];
	}
	else
#endif//__SPU__
	if (nFlags == FSL_READ)
	{
		RelinkTail(m_Dirty[threadId], m_MeshDirtyList[threadId]); 
		if (m_IBStream.m_pUpdateData)
		{
			pD = (byte *)m_IBStream.m_pUpdateData;
			return (uint16 *)&pD[nOffset];
		}
#ifndef __SPU__
#if defined(USE_VBIB_PUSH_DOWN) && !defined(XENON)
		else
		{
			if(m_IBStream.m_DevBuf.m_nID>=0)
			{
				int devOffset;
				uint32 nSize;
				D3DIndexBuffer *pIB;
				{
					CConditonalDevManLock lock(m_nThreadAccessCounter);
					SDevBuffer *pDev = gRenDev->m_DevBufMan.GetDevIB(m_IBStream.m_DevBuf.m_nID);
					devOffset = pDev->m_nDevOffset;
					pIB = pDev->m_D3DBuf.m_pIB;
					nSize = pDev->m_nStagedSize;
				}
				if(nSize && pIB)
				{
					pD = reinterpret_cast<byte*>(AllocateMeshData(nSize,TARGET_DEFAULT_ALIGN));
					if (pD)
					{
						uint8*const pDevBuf = (uint8*)pIB->RawData()+devOffset;						
						pIB->Sync();//sync resource upload
						if(!VidMemPushDown(pD,pDevBuf,nSize))
						{
							FreeMeshData(pD);
							return NULL;
						}					
						m_IBStream.m_nLockFlags |= FSL_VBIBPUSHDOWN;
						m_IBStream.m_pUpdateData = pD;
						return (uint16 *)&pD[nOffset];
					}
					return NULL; 
				}
			}
		}
#endif
		nFlags = FSL_READ | FSL_VIDEO;
#endif//__SPU__
	}
#ifndef __SPU__
	if (nFlags == (FSL_READ | FSL_VIDEO))
	{
		RelinkTail(m_Dirty[threadId], m_MeshDirtyList[threadId]); 
		int nIB = m_IBStream.m_DevBuf.m_nID;
		if (nIB < 0)
			return NULL;
		#if !defined(__SPU__)  // Always read from video buffer on x360
		if (gRenDev->m_pRT && gRenDev->m_pRT->IsMultithreaded())
		{
			// Always use system copy in MT mode
			goto lSysUpdate;
		}
		else
	#endif
		{
			// TODO: make smart caching mesh algorithm for consoles
			if (!m_IBStream.m_pLockedData)
			{
				MESSAGE_VIDEO_BUFFER_ACC_ATTEMPT;
				m_IBStream.m_pLockedData = gRenDev->m_DevBufMan.LockIB(nIB, 0, 0, FSL_READ);
				m_IBStream.m_nLockFlags |= FSL_LOCKED;
			}

			if (m_IBStream.m_pLockedData)
			{
	#if defined(USE_VBIB_PUSH_DOWN) && !defined (XENON)
				if(m_IBStream.m_DevBuf.m_nID>=0)
				{
					uint32 nSize;
					D3DIndexBuffer *pIB;
					{
						CConditonalDevManLock lock(m_nThreadAccessCounter);
						SDevBuffer *pDev = gRenDev->m_DevBufMan.GetDevIB(m_IBStream.m_DevBuf.m_nID);
						pIB = pDev->m_D3DBuf.m_pIB;
						nSize = pDev->m_nStagedSize;
					}
					if(nSize)
					{
						pD = reinterpret_cast<byte*>(AllocateMeshData(nSize,TARGET_DEFAULT_ALIGN));
						if (pD)
						{
							assert( pD );							
							m_VBIBFramePushID = gRenDev->m_RP.m_TI[gRenDev->m_RP.m_nFillThreadID].m_nFrameUpdateID;
							if(!VidMemPushDown(pD,m_IBStream.m_pLockedData,nSize))
							{
								FreeMeshData(pD);
								return NULL;
							}						
							m_IBStream.m_nLockFlags |= FSL_VBIBPUSHDOWN;
							m_IBStream.m_pUpdateData = pD;
							return (uint16 *)&pD[nOffset];
						}
						return NULL; 
					}
				}
	#endif
				pD = (byte *)m_IBStream.m_pLockedData;
				return (uint16 *)&pD[nOffset];
			}
		}
	}

	if (nFlags == (FSL_VIDEO_CREATE))
	{
		// Only xbox and ps3 support direct uploading to vram, but as the data is not 
		// double buffered, only creation is supported 
		// DX11 has to use the deferred context to call map, which is not threadsafe 
		// DX9 can experience huge stalls if resources are used while rendering is performed
		int nIB = -1;
#   if BUFFER_ENABLE_DIRECT_ACCESS 
		nIB = m_IBStream.m_DevBuf.m_nID;
		if (nIB >= 0 && (m_IBStream.m_nFrameCreate || m_IBStream.m_nElements != m_nInds))
#   endif 
			goto lSysCreate;
		RelinkTail(m_Modified[threadId], m_MeshModifiedList[threadId]); 
		if (m_IBStream.m_DevBuf.m_nID < 0) 
		{ 
			nIB = (m_IBStream.m_DevBuf.m_nID = gRenDev->m_DevBufMan.CreateIB(m_nInds*sizeof(uint16)));		
#     ifndef NULL_RENDERER
			m_IBStream.m_DevBuf.m_pDevBuf = gRenDev->m_DevBufMan.GetD3DIB(nIB, &m_IBStream.m_DevBuf.m_nOffset);
#     endif
			m_IBStream.m_nFrameCreate = nFrame; 
		}
		if (nIB < 0)
			return NULL; 
		m_IBStream.m_nElements = m_nInds;
		if (!m_IBStream.m_pLockedData)
		{
			MESSAGE_VIDEO_BUFFER_ACC_ATTEMPT;
			if ((m_IBStream.m_pLockedData = gRenDev->m_DevBufMan.LockIB(nIB, 0, 0, FSL_DIRECT|FSL_WRITE|FSL_DISCARD)) == NULL)
				return NULL;
		}
		pD = (byte*)m_IBStream.m_pLockedData;
		return (uint16*)&pD[nOffset];		
	}

	if (nFlags == (FSL_VIDEO_UPDATE))
	{
		// This code path will be enabled for xbox and ps3 as soon as dynamic meshes have been restored
		goto lSysUpdate;
	}

	assert(0);
	#endif//__SPU__
	return NULL;
}

#ifndef __SPU__
ILINE void CRenderMesh::UnlockVB(int nStream)
{
	if (m_VBStream[nStream].m_nLockFlags & FSL_LOCKED)
	{
		AUTO_LOCK(m_sResLock);
		assert(!gRenDev->m_pRT->IsMultithreaded());
		m_VBStream[nStream].m_nLockFlags &= ~FSL_LOCKED;
		gRenDev->m_DevBufMan.UnlockVB(m_VBStream[nStream].m_DevBuf.m_nID);
	}
	if ((m_VBStream[nStream].m_nLockFlags & FSL_WRITE) && (m_VBStream[nStream].m_nLockFlags & (FSL_SYSTEM_CREATE | FSL_SYSTEM_UPDATE)))
	{
		m_VBStream[nStream].m_nLockFlags &= ~(FSL_SYSTEM_CREATE | FSL_SYSTEM_UPDATE);
		m_VBStream[nStream].m_nFrameRequest = gRenDev->m_RP.m_TI[gRenDev->m_RP.m_nFillThreadID].m_nFrameUpdateID;
	}
}

ILINE void CRenderMesh::UnlockIB()
{
	if (m_IBStream.m_nLockFlags & FSL_LOCKED)
	{
		AUTO_LOCK(m_sResLock);
		assert(!gRenDev->m_pRT->IsMultithreaded());
		m_IBStream.m_nLockFlags &= ~FSL_LOCKED;
		gRenDev->m_DevBufMan.UnlockIB(m_IBStream.m_DevBuf.m_nID);
	}
	if ((m_IBStream.m_nLockFlags & FSL_WRITE) && (m_IBStream.m_nLockFlags & (FSL_SYSTEM_CREATE | FSL_SYSTEM_UPDATE)))
	{
		m_IBStream.m_nLockFlags &= ~(FSL_SYSTEM_CREATE | FSL_SYSTEM_UPDATE);
		m_IBStream.m_nFrameRequest = gRenDev->m_RP.m_TI[gRenDev->m_RP.m_nFillThreadID].m_nFrameUpdateID;
	}
}

void CRenderMesh::UnlockStream(int nStream)
{
	UnlockVB(nStream);
	//if (m_VBStream[nStream].m_nLockFlags & FSL_WRITE)
	//  m_VBStream[nStream].m_nFrameUpdate = m_VBStream[nStream].m_nFrameRequest-1;
	AUTO_LOCK(m_sResLock);
	#ifdef FP16_MESH
	if (nStream == VSF_GENERAL && (m_nFlagsCachePos & FSL_WRITE) && m_pCachePos)
	{
		uint32 i;
		#ifdef KEEP_POSITIONS_INDICES
		if (!m_pSysPosData)
			m_pSysPosData = AllocateMeshData<Vec3f16>(m_nVerts);
		for (i=0; i<m_nVerts; i++)
		{
			m_pSysPosData[i] = m_pCachePos[i];
		}
		#endif
		int nStride;
		byte *pDst = (byte *)LockVB(nStream, m_nFlagsCachePos, 0, m_nVerts, &nStride);
		assert(pDst);
		if (pDst)
		{
			for (i=0; i<m_nVerts; i++)
			{
				Vec3f16 *pVDst = (Vec3f16 *)pDst;
				*pVDst = m_pCachePos[i];
				pDst += nStride;
			}
		}
		m_nFlagsCachePos = 0;
	}
	#endif
	m_VBStream[nStream].m_nLockFlags &= ~(FSL_WRITE | FSL_READ | FSL_SYSTEM | FSL_VIDEO);
}

	void CRenderMesh::UnlockIndexStream()
	{
		UnlockIB();
		//if (m_IBStream.m_nLockFlags & FSL_WRITE)
		//  m_IBStream.m_nFrameUpdate = m_IBStream.m_nFrameRequest-1;
		m_IBStream.m_nLockFlags &= ~(FSL_WRITE | FSL_READ | FSL_SYSTEM | FSL_VIDEO);
	}

bool CRenderMesh::CopyIBToSystemForUpdate()
{
	//ASSERT_IS_MAIN_THREAD(gRenDev->m_pRT)
	if (!m_IBStream.m_pUpdateData)
	{
		AUTO_LOCK(m_sResLock);

		uint32 nSize = m_nInds * sizeof(uint16);
		int nIB = m_IBStream.m_DevBuf.m_nID;
		if (nIB < 0)
			return false;

		void *pSrc = m_IBStream.m_pLockedData;

		if (!pSrc)
		{
	#if defined(USE_VBIB_PUSH_DOWN)
			pSrc = gRenDev->m_DevBufMan.LockIB(nIB, 0 ,0, FSL_READ);
	#else
			pSrc = gRenDev->m_DevBufMan.LockIB(nIB);
	#endif
			m_IBStream.m_nLockFlags |= FSL_LOCKED;
		}

		assert(pSrc);

		if (!pSrc)
			return false;

		byte *pD = reinterpret_cast<byte*>(AllocateMeshData(nSize, TARGET_DEFAULT_ALIGN, false));

		if (pD)
		{
		#if defined(USE_VBIB_PUSH_DOWN)			
			m_IBStream.m_pLockedData = pSrc;
			if(m_IBStream.m_pLockedData)
			{
			m_VBIBFramePushID = gRenDev->m_RP.m_TI[gRenDev->m_RP.m_nFillThreadID].m_nFrameUpdateID;				
			if(!VidMemPushDown(pD,m_IBStream.m_pLockedData,nSize))
			{
				FreeMeshData(pD);
				return false;
			}
					// reset Source ptr since the rsx address gets invalid in the unlockib call
					if (m_IBStream.m_nLockFlags & FSL_LOCKED)
					{
						pSrc = NULL;
					}				
			}
			else
		#endif
			cryMemcpy(pD, pSrc, m_nInds * sizeof(uint16));
			if (m_IBStream.m_nLockFlags & FSL_LOCKED)
			{
				m_IBStream.m_nLockFlags &= ~FSL_LOCKED;
				gRenDev->m_DevBufMan.UnlockIB(nIB);				
			}
			m_IBStream.m_pUpdateData = pD;
			m_IBStream.m_pLockedData = pSrc;
			return true;
		}
	}
	return false;
}

bool CRenderMesh::CopyVBToSystemForUpdate(int nStream)
{
	//ASSERT_IS_MAIN_THREAD(gRenDev->m_pRT)

	if (!m_VBStream[nStream].m_pUpdateData)
	{
		AUTO_LOCK(m_sResLock);
		SMeshStream& MS = m_VBStream[nStream];
		uint32 nSize = GetStreamSize(nStream);
		int nVB = MS.m_DevBuf.m_nID;
		if (nVB < 0)
			return false;

		void *pSrc = MS.m_pLockedData;
		if (!pSrc)
		{
	#if defined(USE_VBIB_PUSH_DOWN)
			pSrc = gRenDev->m_DevBufMan.LockVB(nVB, 0 ,0, FSL_READ);
	#else
			pSrc = gRenDev->m_DevBufMan.LockVB(nVB);
	#endif
			MS.m_nLockFlags |= FSL_LOCKED;
		}
		assert(pSrc);
		if (!pSrc)
			return false;

		byte *pD = reinterpret_cast<byte*>(AllocateMeshData(nSize, TARGET_DEFAULT_ALIGN, false));
		if (pD) 
		{ 
	#if defined(USE_VBIB_PUSH_DOWN)
			MS.m_pLockedData = pSrc;
			if(MS.m_pLockedData)
			{
				m_VBIBFramePushID = gRenDev->m_RP.m_TI[gRenDev->m_RP.m_nFillThreadID].m_nFrameUpdateID;
				if(!VidMemPushDown(pD,MS.m_pLockedData,nSize))
				{
					FreeMeshData(pD);
					return false;
				}
	//			MS.m_nLockFlags |= FSL_VBIBPUSHDOWN;//would make MS.m_pUpdateData be deleted again, check deletion
				if (MS.m_nLockFlags & FSL_LOCKED)
				{
					pSrc = NULL;
				}
			}
			else
	#endif
			cryMemcpy(pD, pSrc, nSize);
			if (MS.m_nLockFlags & FSL_LOCKED)
			{
				MS.m_nLockFlags &= ~FSL_LOCKED;
				gRenDev->m_DevBufMan.UnlockVB(nVB);
			}
			MS.m_pLockedData = pSrc;
			MS.m_pUpdateData = pD;
			m_nFlags |= FRM_READYTOUPLOAD;
			return true;
		}
	}
	return false; 
}

size_t CRenderMesh::SetMesh_Int(CMesh &mesh, int nSecColorsSetOffset, uint32 flags, const Vec3 *pPosOffset)	
{
	LOADING_TIME_PROFILE_SECTION;

	MEMSTAT_CONTEXT(EMemStatContextTypes::MSC_RenderMeshType, 0, this->GetTypeName());
	MEMSTAT_CONTEXT(EMemStatContextTypes::MSC_RenderMesh, 0, this->GetSourceName());

	SVF_P3S_C4B_T2S *pVBuff = NULL;
	SVF_P2S_N4B_C4B_T1F *pVBuffV = NULL;
	SVF_C4B_C4B *pSHBuf = NULL;
	SPipTangents *pTBuff = NULL;
	SQTangents *pQTBuff = NULL;
	SVF_P3F *pMorphTargets = NULL;
	SVF_P3F_P3F_I4B *pShapeDeformBuff = NULL;
	Vec3 *pNBuff = NULL;
	uint32 nVerts = mesh.GetVertexCount();
	uint32 nInds = mesh.GetIndexCount();
	uint16 *pInds = NULL;

	//AUTO_LOCK(m_sResLock);//need a resource lock as mesh could be reseted due to allocation failure
	LockForThreadAccess();
	
	ReleaseRenderChunks(m_pChunksSkinned);
	SAFE_DELETE(m_pChunksSkinned);

	m_vBoxMin = mesh.m_bbox.min;
	m_vBoxMax = mesh.m_bbox.max;

	//////////////////////////////////////////////////////////////////////////
	// Initialize Render Chunks.
	//////////////////////////////////////////////////////////////////////////
	uint32 numSubsets = mesh.GetSubSetCount();

	uint32 numChunks = 0;
	for (uint32 i=0; i<numSubsets; i++)
	{
		if (mesh.m_subsets[i].nNumIndices == 0)
			continue;

		if(mesh.m_subsets[i].nMatFlags & MTL_FLAG_NODRAW)
			continue;

		++ numChunks;
	}

	m_Chunks.reserve(numChunks);

	for (uint32 i=0; i<numSubsets; i++)
	{
		CRenderChunk ChunkInfo;

		if (mesh.m_subsets[i].nNumIndices == 0)
			continue;

		if(mesh.m_subsets[i].nMatFlags & MTL_FLAG_NODRAW)
			continue;

		//add empty chunk, because PodArray is not working with STL-vectors
		m_Chunks.Add(ChunkInfo);

		uint32 num = m_Chunks.Count();
		CRenderChunk* pChunk = &m_Chunks[num-1];

		pChunk->nFirstIndexId = mesh.m_subsets[i].nFirstIndexId;
		pChunk->nNumIndices   = mesh.m_subsets[i].nNumIndices;
		pChunk->nFirstVertId  = mesh.m_subsets[i].nFirstVertId;
		pChunk->nNumVerts     = mesh.m_subsets[i].nNumVerts;
		pChunk->m_nMatID      = mesh.m_subsets[i].nMatID;
		pChunk->m_nMatFlags   = mesh.m_subsets[i].nMatFlags;
		if (mesh.m_subsets[i].nPhysicalizeType==PHYS_GEOM_TYPE_NONE)
			pChunk->m_nMatFlags |= MTL_FLAG_NOPHYSICALIZE;

		float texelAreaDensity = 1.0f;
	
		if(!(flags & FSM_VOXELS) && !(flags & FSM_IGNORE_TEXELDENSITY))
		{
			float posArea;
			float texArea;
			const char* errorText = "";

			const bool ok = mesh.ComputeSubsetTexMappingAreas(i, posArea, texArea, errorText);
			if (ok)
			{
				texelAreaDensity = texArea / posArea;
			}
			else
			{
				// Commented out to prevent spam (contact Moritz Finck or Marco Corbetta for details)
				// gEnv->pLog->LogError("Failed to compute texture mapping density for mesh '%s': ?%s", GetSourceName(), errorText);
			}
		}

		pChunk->m_texelAreaDensity = texelAreaDensity;

	#define VALIDATE_CHUCKS
	#if defined(_DEBUG) && defined(VALIDATE_CHUCKS)
		size_t indStart( pChunk->nFirstIndexId );
		size_t indEnd( pChunk->nFirstIndexId + pChunk->nNumIndices );
		for( size_t j( indStart ); j < indEnd; ++j )
		{
			size_t vtxStart( pChunk->nFirstVertId );
			size_t vtxEnd( pChunk->nFirstVertId + pChunk->nNumVerts );
			size_t curIndex0( mesh.m_pIndices[ j ] ); // absolute indexing
			size_t curIndex1( mesh.m_pIndices[ j ] + vtxStart ); // relative indexing using base vertex index
			assert( ( curIndex0 >= vtxStart && curIndex0 < vtxEnd ) || ( curIndex1 >= vtxStart && curIndex1 < vtxEnd ) ) ;
		}
	#endif

		if (mesh.m_pBoneMapping)
		{
			pChunk->m_arrChunkBoneIDs  = mesh.m_subsets[i].m_arrGlobalBonesPerSubset;
		}
	}

	//////////////////////////////////////////////////////////////////////////
	// Create RenderElements.
	//////////////////////////////////////////////////////////////////////////
	int nCurChunk = 0;
	for (int i=0; i<mesh.GetSubSetCount(); i++)
	{
		SMeshSubset &subset = mesh.m_subsets[i];
		if (subset.nNumIndices == 0)
			continue;

		if(subset.nMatFlags & MTL_FLAG_NODRAW)
			continue;

		CRenderChunk *pRenderChunk = &m_Chunks[nCurChunk++];
		CREMeshImpl *pRenderElement = (CREMeshImpl*) gRenDev->EF_CreateRE(eDATA_Mesh);

		// Cross link render chunk with render element.
		pRenderChunk->pRE = pRenderElement;
		AssignChunk(pRenderChunk, pRenderElement);
		if (subset.nNumVerts <= 500 && !mesh.m_pBoneMapping && !mesh.m_pShapeDeformation && !(flags & FSM_VOXELS) && !(flags & FSM_NO_TANGENTS))
			pRenderElement->mfUpdateFlags(FCEF_MERGABLE);

		if (mesh.m_pBoneMapping || mesh.m_pShapeDeformation)
			pRenderElement->mfUpdateFlags(FCEF_SKINNED);
	}

	if (mesh.m_pBoneMapping || mesh.m_pShapeDeformation)
		m_nFlags |= FRM_SKINNED;

	//////////////////////////////////////////////////////////////////////////
	// Create system vertex buffer in system memory.
	//////////////////////////////////////////////////////////////////////////
	#ifdef FP16_MESH
	if (!(flags & FSM_VOXELS) && nVerts < RM_MAXVERTS_FOR_INSTANCING && CRenderer::CV_r_geominstancing == 3)
		m_nFlags |= FRM_INSTANCED;
	#endif
	#ifdef TESSELLATION_RENDERER
	if (flags & FSM_SUPPORT_HW_TESSELLATION)
		m_nFlags |= FRM_SUPPORT_HW_TESSELLATION;
	#endif

	m_nVerts = nVerts;
	m_nInds = 0;

	if (!(flags & FSM_VOXELS))
	{
		m_eVF = eVF_P3S_C4B_T2S;
		pVBuff = (SVF_P3S_C4B_T2S *)LockVB(VSF_GENERAL, FSL_VIDEO_CREATE);
		// stop initializing if allocation failed
		if( pVBuff == NULL ) 
		{
			m_nVerts = 0; 
			goto error; 
		}
		
	}
	else
	{
		m_eVF = eVF_P2S_N4B_C4B_T1F;
		pVBuffV = (SVF_P2S_N4B_C4B_T1F *)LockVB(VSF_GENERAL, FSL_VIDEO_CREATE);
		// stop initializing if allocation failed
		if( pVBuffV == NULL ) 
		{ 
			m_nVerts = 0; 
			goto error; 
		}	
	}

# if ENABLE_NORMALSTREAM_SUPPORT
	if (m_nFlags & FRM_SUPPORT_HW_TESSELLATION)
		pNBuff = (Vec3 *)LockVB(VSF_NORMALS, FSL_VIDEO_CREATE);
# endif

	if (!(flags & FSM_NO_TANGENTS))
	{
		if (mesh.m_pQTangents)
			pQTBuff = (SQTangents *)LockVB(VSF_QTANGENTS, FSL_VIDEO_CREATE);
		else
			pTBuff = (SPipTangents *)LockVB(VSF_TANGENTS, FSL_VIDEO_CREATE);
		
		// stop initializing if allocation failed
		if (pTBuff == NULL && pQTBuff == NULL)
			goto error;
	}

	//////////////////////////////////////////////////////////////////////////
	// Copy indices.
	//////////////////////////////////////////////////////////////////////////
	m_nInds = nInds; 
	pInds = LockIB(FSL_VIDEO_CREATE);

	// stop initializing if allocation failed
	if( m_nInds && pInds == NULL )
	{
		m_nInds = 0;
		goto error;
	}

	//////////////////////////////////////////////////////////////////////////
	// create buffer for morph-targets.
	//////////////////////////////////////////////////////////////////////////	
	if (flags & FSM_MORPH_TARGETS)
	{
		pMorphTargets = (SVF_P3F *)LockVB(VSF_HWSKIN_MORPHTARGET_INFO, FSL_VIDEO_CREATE);		

		// stop initializing if allocation failed
		if (pMorphTargets == NULL) 
			goto error; 		
	}


#if !defined(XENON) && !defined(PS3)	
	if (mesh.m_pShapeDeformation) 
	{
		pShapeDeformBuff = (SVF_P3F_P3F_I4B *)LockVB(VSF_HWSKIN_SHAPEDEFORM_INFO, FSL_VIDEO_CREATE);	

		// stop initializing if allocation failed
		if (pShapeDeformBuff == NULL) 
			goto error; 	
	}
#endif

	// Copy data to mesh 
	{
		SSetMeshIntData setMeshIntData = {  &mesh, pVBuff, pVBuffV, pSHBuf, pTBuff, pQTBuff, pMorphTargets, nVerts, nInds, pInds, pPosOffset, flags, pShapeDeformBuff, pNBuff };
		bool bIsStreamingThread = !gRenDev->m_pRT->IsMainThread() && !gRenDev->m_pRT->IsRenderThread(); 
		if( bIsStreamingThread ) 
		{
			TRenderMesh_SetMeshJob job( setMeshIntData );
			job.SetCacheMode(JobManager::SPUBackend::eCM_32); 	
			job.SetClassInstance(this);
			job.SetJobType(JobManager::SPUBackend::eOffLoadJob);			
			job.Run();			
		}
		else
		{
			SetMesh_IntImpl( setMeshIntData );
		}
	}


	// unlock all streams
	UnlockVB(VSF_GENERAL);
#if ENABLE_NORMALSTREAM_SUPPORT
	if (m_nFlags & FRM_SUPPORT_HW_TESSELLATION)
		UnlockVB(VSF_NORMALS);
# endif

	UnlockIB();

	if (!(flags & FSM_NO_TANGENTS))
	{
		if (mesh.m_pQTangents)
			UnlockVB(VSF_QTANGENTS);
		else
			UnlockVB(VSF_TANGENTS);
	}

	if (flags & FSM_MORPH_TARGETS)
	{		
		UnlockVB(VSF_HWSKIN_MORPHTARGET_INFO);
	}

#if !defined(XENON) && !defined(PS3) 
	if (mesh.m_pShapeDeformation) 
	{		
		UnlockVB(VSF_HWSKIN_SHAPEDEFORM_INFO);
	}
#endif
 	
	
	//////////////////////////////////////////////////////////////////////////
	// Copy skin-streams.
	//////////////////////////////////////////////////////////////////////////
	if (mesh.m_pBoneMapping) 
		SetSkinningDataCharacter(mesh, mesh.m_pBoneMapping);
 		
	// Create device buffers immediately in non-multithreaded mode
	if (!gRenDev->m_pRT->IsMultithreaded() && (flags & FSM_CREATE_DEVICE_MESH))
	{
		CheckUpdate(m_eVF, VSM_MASK);
	}

	UnLockForThreadAccess();
	return Size(SIZE_ONLY_SYSTEM);

error:
	UnLockForThreadAccess();
	RT_AllocationFailure();
	return ~0U; 
}

#endif // __SPU__

#if !defined(__SPU__)
size_t CRenderMesh::SetMesh(CMesh &mesh, int nSecColorsSetOffset, uint32 flags, const Vec3 *pPosOffset, bool requiresLock)
{
	LOADING_TIME_PROFILE_SECTION;

	size_t resultingSize = ~0U;
#ifdef USE_VBIB_PUSH_DOWN
	{
		AUTO_LOCK(m_sResLock);
		resultingSize = SetMesh_Int(mesh, nSecColorsSetOffset, flags, pPosOffset);
	}
#else
	if(requiresLock)
	{
		AUTO_LOCK(m_sResLock);
		resultingSize = SetMesh_Int(mesh, nSecColorsSetOffset, flags, pPosOffset);
	}
	else
	{
		resultingSize = SetMesh_Int(mesh, nSecColorsSetOffset, flags, pPosOffset);
	}
#endif

	return resultingSize;
}

void CRenderMesh::SetSkinningDataVegetation(struct SMeshBoneMapping *pBoneMapping)
{
	MEMSTAT_CONTEXT(EMemStatContextTypes::MSC_RenderMeshType, 0, this->GetTypeName());
	MEMSTAT_CONTEXT(EMemStatContextTypes::MSC_RenderMesh, 0, this->GetSourceName());

	//  struct_VERTEX_FORMAT_WEIGHTS4UB_INDICES4UB *pSkinBuff = new struct_VERTEX_FORMAT_WEIGHTS4UB_INDICES4UB[nVerts];
	LockForThreadAccess();
	SVF_W4B_I4B *pSkinBuff = (SVF_W4B_I4B *)LockVB(VSF_HWSKIN_INFO, FSL_VIDEO_CREATE);
	
	// stop initializing if allocation failed
	if( pSkinBuff == NULL ) 
		return;
		 
	for (uint32 i = 0; i < m_nVerts; i++ )
	{
		// get bone IDs
		uint16 b0 = pBoneMapping[i].boneIDs[0];
		uint16 b1 = pBoneMapping[i].boneIDs[1];
		uint16 b2 = pBoneMapping[i].boneIDs[2];
		uint16 b3 = pBoneMapping[i].boneIDs[3];

		// get weights
		f32 w0 = pBoneMapping[i].weights[0];
		f32 w1 = pBoneMapping[i].weights[1];
		f32 w2 = pBoneMapping[i].weights[2];
		f32 w3 = pBoneMapping[i].weights[3];

		// if weight is zero set bone ID to zero as the bone has no influence anyway,
		// this will fix some issue with incorrectly exported models (e.g. system freezes on ATI cards when access invalid bones)
		if (w0 == 0) b0 = 0;
		if (w1 == 0) b1 = 0;
		if (w2 == 0) b2 = 0;
		if (w3 == 0) b3 = 0;											

		pSkinBuff[i].indices.bcolor[0] = (uint8)b0;
		pSkinBuff[i].indices.bcolor[1] = (uint8)b1;
		pSkinBuff[i].indices.bcolor[2] = (uint8)b2;
		pSkinBuff[i].indices.bcolor[3] = (uint8)b3;

		// copy weights

		pSkinBuff[i].weights.bcolor[0] = (uint8)w0;
		pSkinBuff[i].weights.bcolor[1] = (uint8)w1;
		pSkinBuff[i].weights.bcolor[2] = (uint8)w2;
		pSkinBuff[i].weights.bcolor[3] = (uint8)w3;

	//  if (pBSStreamTemp)
	//    pSkinBuff[i].boneSpace  = pBSStreamTemp[i];
	}
	UnlockVB(VSF_HWSKIN_INFO);
	UnLockForThreadAccess();
}

void CRenderMesh::SetSkinningDataCharacter(CMesh& mesh, struct SMeshBoneMapping *pBoneMapping)
{
	MEMSTAT_CONTEXT(EMemStatContextTypes::MSC_RenderMeshType, 0, this->GetTypeName());
	MEMSTAT_CONTEXT(EMemStatContextTypes::MSC_RenderMesh, 0, this->GetSourceName());

	SVF_W4B_I4B *pSkinBuff = (SVF_W4B_I4B *)LockVB(VSF_HWSKIN_INFO, FSL_VIDEO_CREATE);

	// stop initializing if allocation failed
	if( pSkinBuff == NULL ) 
	{
		return; 
	}

	for (uint32 i=0; i<m_nVerts; i++ )
	{
		// get bone IDs
		uint16 b0 = pBoneMapping[i].boneIDs[0];
		uint16 b1 = pBoneMapping[i].boneIDs[1];
		uint16 b2 = pBoneMapping[i].boneIDs[2];
		uint16 b3 = pBoneMapping[i].boneIDs[3];

		// get weights
		f32 w0 = pBoneMapping[i].weights[0];
		f32 w1 = pBoneMapping[i].weights[1];
		f32 w2 = pBoneMapping[i].weights[2];
		f32 w3 = pBoneMapping[i].weights[3];

		// if weight is zero set bone ID to zero as the bone has no influence anyway,
		// this will fix some issue with incorrectly exported models (e.g. system freezes on ATI cards when access invalid bones)
		if (w0 == 0) 
			b0 = 0;
		if (w1 == 0) 
			b1 = 0;
		if (w2 == 0) 
			b2 = 0;
		if (w3 == 0) 
			b3 = 0;											

		pSkinBuff[i].indices.bcolor[0] = (uint8)b0;
		pSkinBuff[i].indices.bcolor[1] = (uint8)b1;
		pSkinBuff[i].indices.bcolor[2] = (uint8)b2;
		pSkinBuff[i].indices.bcolor[3] = (uint8)b3;

		// copy weights

		pSkinBuff[i].weights.bcolor[0] = (uint8)w0;
		pSkinBuff[i].weights.bcolor[1] = (uint8)w1;
		pSkinBuff[i].weights.bcolor[2] = (uint8)w2;
		pSkinBuff[i].weights.bcolor[3] = (uint8)w3;

		//   if (pBSStreamTemp)
		//     pSkinBuff[i].boneSpace  = pBSStreamTemp[i];
	}
	UnlockVB(VSF_HWSKIN_INFO);
}

IIndexedMesh *CRenderMesh::GetIndexedMesh(IIndexedMesh *pIdxMesh)
{
	struct MeshDataLock 
	{ 
		MeshDataLock(CRenderMesh *pMesh) : _Mesh(pMesh) 
		{ 
			_Mesh->LockForThreadAccess(); 
		} 

		~MeshDataLock() 
		{ 
			_Mesh->UnLockForThreadAccess(); 
		} 

		CRenderMesh *_Mesh; 
	}; 

	MeshDataLock _lock(this); 

	if (!pIdxMesh)
		pIdxMesh = gEnv->p3DEngine->CreateIndexedMesh();

	// catch failed allocation of IndexedMesh
	if( pIdxMesh == NULL )
		return NULL;

	CMesh *pMesh = pIdxMesh->GetMesh();
	uint32 i;
	int j;

	pIdxMesh->SetVertexCount(m_nVerts);
	pIdxMesh->SetTexCoordsAndTangentsCount(m_nVerts);
	pIdxMesh->SetIndexCount(m_nInds);
	pIdxMesh->SetSubSetCount(m_Chunks.size());

	strided_pointer<Vec3> pVtx;
	strided_pointer<Vec4sf> pTang,pBinorm;
	strided_pointer<Vec2f16> pTex;
	pVtx.data = (Vec3*)GetPosPtr(pVtx.iStride, FSL_READ);
	//pNorm.data = (Vec3*)GetNormalPtr(pNorm.iStride);
	pTex.data = (Vec2f16*)GetUVPtr(pTex.iStride, FSL_READ);
	pTang.data = (Vec4sf*)GetTangentPtr(pTang.iStride, FSL_READ);
	pBinorm.data = (Vec4sf*)GetBinormalPtr(pBinorm.iStride, FSL_READ);

	// don't copy if some src, or dest buffer is NULL (can happen because of failed allocations)
	if(		pVtx.data == NULL			|| (pMesh->m_pPositions == NULL && pMesh->m_pPositionsF16 == NULL) ||
				pTex.data == NULL			|| pMesh->m_pTexCoord == NULL ||
				pTang.data == NULL		|| pMesh->m_pTangents  == NULL ||
				pBinorm.data == NULL )
	{		
		UnlockStream(VSF_GENERAL);
		delete pIdxMesh;
		return NULL; 
	}

	for(i=0;i<m_nVerts;i++)
	{
		pMesh->m_pPositions[i] = pVtx[i];
		pMesh->m_pNorms[i] = Vec3(0,0,1); //pNorm[i];
		Vec2 st = pTex[i].ToVec2();
		pMesh->m_pTexCoord[i].s = st.x;
		pMesh->m_pTexCoord[i].t = st.y;
		pMesh->m_pTangents[i].Binormal = pBinorm[i];
		pMesh->m_pTangents[i].Tangent = pTang[i];
	}

	if (m_eVF==eVF_P3S_C4B_T2S || m_eVF==eVF_P3S_N4B_C4B_T2S)
	{
		strided_pointer<SMeshColor> pColors;
		pColors.data = (SMeshColor*)GetColorPtr(pColors.iStride, FSL_READ);
		pIdxMesh->SetColorsCount(m_nVerts);
		for(i=0; i<m_nVerts; i++) 
		{
			pMesh->m_pColor0[i] = pColors[i];
		}
	}
	UnlockStream(VSF_GENERAL);

	uint16 *pInds = GetIndexPtr(FSL_READ);
	for(i=0;i<(int)m_nInds;i++)
		pMesh->m_pIndices[i] = pInds[i];
	UnlockIndexStream(); 

	SVF_W4B_I4B *pSkinBuff = (SVF_W4B_I4B*)LockVB(VSF_HWSKIN_INFO, FSL_READ);
	if (pSkinBuff)
	{
		pIdxMesh->AllocateBoneMapping();
		for(i=0;i<m_nVerts;i++) for(j=0;j<4;j++)
		{
			pMesh->m_pBoneMapping[i].boneIDs[j] = pSkinBuff[i].indices.bcolor[j];
			pMesh->m_pBoneMapping[i].weights[j] = pSkinBuff[i].weights.bcolor[j];
		}
		UnlockVB(VSF_HWSKIN_INFO);
	}

	for(i=0;i<(int)m_Chunks.size();i++)
	{
		pIdxMesh->SetSubsetIndexVertexRanges(i, m_Chunks[i].nFirstIndexId, m_Chunks[i].nNumIndices, m_Chunks[i].nFirstVertId, m_Chunks[i].nNumVerts);
		pIdxMesh->SetSubsetMaterialId(i, m_Chunks[i].m_nMatID);
		const int nMatFlags = m_Chunks[i].m_nMatFlags;
		const int nPhysicalizeType = (nMatFlags & MTL_FLAG_NOPHYSICALIZE) 
			? PHYS_GEOM_TYPE_NONE 
			: ((nMatFlags & MTL_FLAG_NODRAW) ? PHYS_GEOM_TYPE_OBSTRUCT : PHYS_GEOM_TYPE_DEFAULT);
		pIdxMesh->SetSubsetMaterialProperties(i, nMatFlags, nPhysicalizeType);
		pIdxMesh->SetSubsetBoneIds(i,m_Chunks[i].m_arrChunkBoneIDs);

		const SMeshSubset &mss = pIdxMesh->GetSubSet(i);
		Vec3 vCenter;
		vCenter.zero();
		for(j=mss.nFirstIndexId; j<mss.nFirstIndexId+mss.nNumIndices; j++)
		{
			vCenter += pMesh->m_pPositions[pMesh->m_pIndices[j]];
		}
		if (mss.nNumIndices)
		{
			vCenter /= (float)mss.nNumIndices;
		}
		float fRadius = 0;
		for(j=mss.nFirstIndexId; j<mss.nFirstIndexId+mss.nNumIndices; j++)
		{
			fRadius = max(fRadius, (pMesh->m_pPositions[pMesh->m_pIndices[j]]-vCenter).len2());
		}
		fRadius = sqrt_tpl(fRadius);
		pIdxMesh->SetSubsetBounds(i, vCenter, fRadius);
	}

	return pIdxMesh;
}

void CRenderMesh::GenerateQTangents()
{
	// FIXME: This needs to be cleaned up. Breakable foliage shouldn't need both streams, and this shouldn't be duplicated
	// between here and CryAnimation.
	LockForThreadAccess();
	int srcStride = 0;
	void *pSrcD = LockVB(VSF_TANGENTS, FSL_READ, 0, 0, &srcStride);
	if (pSrcD)
	{
		int dstStride = 0;
		void *pDstD = LockVB(VSF_QTANGENTS, FSL_VIDEO_CREATE, 0, 0, &dstStride);
		assert(pDstD);
		if (pDstD)
		{
			Vec4sf *pTangents = (Vec4sf *)pSrcD;
			Vec4sf *pQTangents = (Vec4sf *)pDstD;
      
			// copied from LoaderCHR. Ditto to that comment, this should be in RC!
			for (uint32 e=0; e<m_nVerts; e++)
			{

				Vec3 tangent(
					tPackB2F(pTangents[e*2+0].x),
					tPackB2F(pTangents[e*2+0].y),
					tPackB2F(pTangents[e*2+0].z));
				tangent.Normalize();

				Vec3 binormal(
					tPackB2F(pTangents[e*2+1].x),
					tPackB2F(pTangents[e*2+1].y),
					tPackB2F(pTangents[e*2+1].z));
				binormal.Normalize();

				f32 reflection = tPackB2F(pTangents[e*2+0].w);

				Matrix33 m;
				m.SetRow(0, tangent);
				m.SetRow(1, binormal);
				m.SetRow(2, tangent.Cross(binormal).GetNormalized());
				m.OrthonormalizeFast();

				uint32 isOrtho = m.IsOrthonormalRH(0.1f);
				if (isOrtho==0)
				{
					m.SetIdentity();  //hack to avoid a Division by 0 because of a broken tangent-matrix in the model-mesh
					//ANIM_ASSET_CHECK_TRACE(isOrtho != 0, ("Invalid tangent-matrix in model: %s, vertex position x=%f;y=%f;z=%f", strGeomFileName.c_str(), pMesh->m_pPositions[e].x, pMesh->m_pPositions[e].y, pMesh->m_pPositions[e].z));
				}

				Quat quat(m);
				quat.v = -quat.v;

				quat.Normalize();
				if (quat.w < 0.0f)
					quat = -quat;
				if (reflection < 0.0f)
				{
					static const float BIAS_16BIT = 1.0f / 32767.0f;
					static const float BIAS_SCALE_16BIT = sqrtf(1.0f - BIAS_16BIT*BIAS_16BIT);
					if (quat.w < BIAS_16BIT)
					{
						quat *= BIAS_SCALE_16BIT;
						quat.w = BIAS_16BIT;
					}
					quat = -quat;
				}

				int16f qx = tPackF2B(quat.v.x);
				int16f qy = tPackF2B(quat.v.y);
				int16f qz = tPackF2B(quat.v.z);
				int16f qw = tPackF2B(quat.w);

				pQTangents[e].x = tPackF2B(quat.v.x);
				pQTangents[e].y = tPackF2B(quat.v.y);
				pQTangents[e].z = tPackF2B(quat.v.z);
				pQTangents[e].w = tPackF2B(quat.w);

			}
		}
		UnlockVB(VSF_QTANGENTS);
	}
	UnlockVB(VSF_TANGENTS);
	UnLockForThreadAccess();
}

void CRenderMesh::CreateChunksSkinned()
{
	MEMSTAT_CONTEXT(EMemStatContextTypes::MSC_RenderMeshType, 0, this->GetTypeName());
	MEMSTAT_CONTEXT(EMemStatContextTypes::MSC_RenderMesh, 0, this->GetSourceName());

	SAFE_DELETE(m_pChunksSkinned);

	PodArray<CRenderChunk>& arrSrcMats = m_Chunks;
	PodArray<CRenderChunk>& arrNewMats = *(m_pChunksSkinned = new PodArray<CRenderChunk>);
	arrNewMats.resize (arrSrcMats.Size());
	for (uint32 i=0; i<arrSrcMats.size(); ++i)
	{
		CRenderChunk& rSrcMat = arrSrcMats[i]; 
		CRenderChunk& rNewMat = arrNewMats[i];
		rNewMat = rSrcMat;
		CREMeshImpl *re = (CREMeshImpl*) rSrcMat.pRE;
		if (re)
		{
			rNewMat.pRE = (CREMeshImpl*) gRenDev->EF_CreateRE(eDATA_Mesh);
			CRendElement *pNext = rNewMat.pRE->m_NextGlobal;
			CRendElement *pPrev = rNewMat.pRE->m_PrevGlobal;
			*(CREMeshImpl*)rNewMat.pRE = *re;
			if (rNewMat.pRE->m_pChunk) // affects the source mesh!! will only work correctly if the source is deleted after copying
			rNewMat.pRE->m_pChunk = &rNewMat;
			rNewMat.pRE->m_NextGlobal = pNext;
			rNewMat.pRE->m_PrevGlobal = pPrev;
			rNewMat.pRE->m_pRenderMesh = this;
			rNewMat.pRE->m_CustomData = NULL;
		}
	}
}

int CRenderMesh::GetRenderChunksCount(IMaterial * pMaterial, int & nRenderTrisCount)
{
	int nCount = 0;
	nRenderTrisCount = 0;

	CRenderer *rd = gRenDev;
	const uint32 ni = (uint32)m_Chunks.Count();
	for (uint32 i=0; i<ni; i++)     
	{
		CRenderChunk *pChunk = &m_Chunks[i];
		CRendElementBase * pREMesh = pChunk->pRE;

		SShaderItem *pShaderItem = &pMaterial->GetShaderItem(pChunk->m_nMatID);

		SRenderShaderResources *pR = (SRenderShaderResources *)pShaderItem->m_pShaderResources;
		CShader *pS = (CShader *)pShaderItem->m_pShader;
		if (pREMesh && pS && pR)
		{
			if(pChunk->m_nMatFlags & MTL_FLAG_NODRAW)
				continue;

			if (pS->m_Flags2 & EF2_NODRAW)
				continue;

			if(rd->CV_r_VegetationAlphaTestOnly==1 && pS->GetShaderType() == eST_Vegetation)
				if(!pR->GetAlphaRef())
					continue;

			if(pChunk->nNumIndices)
			{
				nRenderTrisCount += pChunk->nNumIndices/3;
				nCount++;
			}
		}
	}

	return nCount;
}

void CRenderMesh::CopyTo(IRenderMesh *_pDst, int nAppendVtx, bool bDynamic)
{
	CRenderMesh *pDst = (CRenderMesh *)_pDst;
	#ifdef USE_VBIB_PUSH_DOWN
	AUTO_LOCK(m_sResLock);
	#endif
	PodArray<CRenderChunk>& arrSrcMats = m_Chunks;
	PodArray<CRenderChunk>& arrNewMats = pDst->m_Chunks;
	//pDst->m_bMaterialsWasCreatedInRenderer  = true;
	arrNewMats.resize (arrSrcMats.Size());
	if (bDynamic)
	pDst->m_eType = eRMT_KeepSystem;

	uint32 i;
	for (i=0; i<arrSrcMats.size(); ++i)
	{
		CRenderChunk& rSrcMat = arrSrcMats[i]; 
		CRenderChunk& rNewMat = arrNewMats[i];
		rNewMat = rSrcMat;
		rNewMat.nNumVerts	+= ((m_nVerts-2-rNewMat.nNumVerts-rNewMat.nFirstVertId)>>31) & nAppendVtx;
		CREMeshImpl* re = (CREMeshImpl*) rSrcMat.pRE;
		if (re)
		{
			rNewMat.pRE = (CREMeshImpl*) gRenDev->EF_CreateRE(eDATA_Mesh);
			CRendElement *pNext = rNewMat.pRE->m_NextGlobal;
			CRendElement *pPrev = rNewMat.pRE->m_PrevGlobal;
			*(CREMeshImpl*)rNewMat.pRE = *re;
			if (rNewMat.pRE->m_pChunk) // affects the source mesh!! will only work correctly if the source is deleted after copying
			{
			rNewMat.pRE->m_pChunk = &rNewMat;
			rNewMat.pRE->m_pChunk->nNumVerts += ((m_nVerts-2-re->m_pChunk->nNumVerts-re->m_pChunk->nFirstVertId)>>31) & nAppendVtx;
			}
			rNewMat.pRE->m_NextGlobal = pNext;
			rNewMat.pRE->m_PrevGlobal = pPrev;
			rNewMat.pRE->m_pRenderMesh = pDst;
			//assert(rNewMat.pRE->m_CustomData);
			rNewMat.pRE->m_CustomData = NULL;
		}
	}

	LockForThreadAccess(); 
	pDst->LockForThreadAccess();

	pDst->m_nVerts = m_nVerts+nAppendVtx;
	pDst->m_eVF = m_eVF;
	for (i=0; i<VSF_NUM; i++)
	{
		void *pSrcD = LockVB(i, FSL_READ);
		if (pSrcD)
		{
			void *pDstD = pDst->LockVB(i, FSL_VIDEO_CREATE);
			assert(pDstD);
			if (pDstD)
				cryMemcpy(pDstD, pSrcD, GetStreamSize(i),MC_CPU_TO_GPU);
			pDst->UnlockVB(i);
		}
		UnlockVB(i);
	}

	pDst->m_nInds = m_nInds;
	void *pSrcD = LockIB(FSL_READ);
	if (pSrcD)
	{
		void *pDstD = pDst->LockIB(FSL_VIDEO_CREATE);
		assert(pDstD);
		if (pDstD)
			cryMemcpy(pDstD, pSrcD, m_nInds*sizeof(uint16),MC_CPU_TO_GPU);
		pDst->UnlockIB();
	}
	UnlockIB();
	UnLockForThreadAccess(); 
	pDst->UnLockForThreadAccess();
}

// set effector for all chunks
void CRenderMesh::SetCustomTexID( int nCustomTID )
{
	if (m_Chunks.Count() && nCustomTID != 0)
	{
		for(int i=0; i<m_Chunks.Count(); i++)
		{
			CRenderChunk *pChunk = &m_Chunks[i];
			//    pChunk->shaderItem.m_pShader = pShader;
			if (pChunk->pRE)
			pChunk->pRE->m_CustomTexBind[0] = nCustomTID;
		}
	}
}

void CRenderMesh::SetChunk(int nIndex, CRenderChunk &inChunk)
{
	MEMSTAT_CONTEXT(EMemStatContextTypes::MSC_RenderMeshType, 0, this->GetTypeName());
	MEMSTAT_CONTEXT(EMemStatContextTypes::MSC_RenderMesh, 0, this->GetSourceName());

	if (!inChunk.nNumIndices || !inChunk.nNumVerts)
		return;

	CRenderChunk *pRenderChunk = NULL;

	if (nIndex < 0 || nIndex >= m_Chunks.Count())
	{
		// add new chunk
		CRenderChunk matinfo;
		m_Chunks.Add(matinfo);
		pRenderChunk = &m_Chunks.back();

		pRenderChunk->pRE = (CREMeshImpl*) gRenDev->EF_CreateRE(eDATA_Mesh);
		pRenderChunk->pRE->m_CustomTexBind[0] = m_nClientTextureBindID;
	}
	else
	{
		// use present chunk
		pRenderChunk = &m_Chunks[nIndex];
	}

	pRenderChunk->m_nMatID = inChunk.m_nMatID;
	pRenderChunk->m_nMatFlags = inChunk.m_nMatFlags;

	pRenderChunk->nFirstIndexId	= inChunk.nFirstIndexId;
	pRenderChunk->nNumIndices		= MAX(inChunk.nNumIndices,0);
	pRenderChunk->nFirstVertId	= inChunk.nFirstVertId;
	pRenderChunk->nNumVerts			= MAX(inChunk.nNumVerts,0);
	pRenderChunk->nSubObjectIndex = inChunk.nSubObjectIndex;

	pRenderChunk->m_texelAreaDensity = inChunk.m_texelAreaDensity;

	// update chunk RE
	if (pRenderChunk->pRE)
	AssignChunk(pRenderChunk, (CREMeshImpl*) pRenderChunk->pRE);
	assert(!pRenderChunk->pRE || pRenderChunk->pRE->m_pChunk->nFirstIndexId<60000);
	assert(pRenderChunk->nFirstIndexId + pRenderChunk->nNumIndices <= m_nInds);
}

void CRenderMesh::SetChunk(IMaterial *pNewMat, int nFirstVertId, int nVertCount, int nFirstIndexId, int nIndexCount, float texelAreaDensity, int nIndex)
{
	CRenderChunk chunk;

	if (pNewMat)
		chunk.m_nMatFlags = pNewMat->GetFlags();

	if (nIndex < 0 || nIndex >= m_Chunks.Count())
		chunk.m_nMatID = m_Chunks.Count();
	else
		chunk.m_nMatID = nIndex;

	chunk.nFirstVertId = nFirstVertId;
	chunk.nNumVerts = nVertCount;

	chunk.nFirstIndexId = nFirstIndexId;
	chunk.nNumIndices = nIndexCount;

	chunk.m_texelAreaDensity = texelAreaDensity;

	SetChunk(nIndex, chunk);
}
#endif//__SPU__
//================================================================================================================

#ifdef FP16_MESH
bool CRenderMesh::PrepareCachePos()
{
	#if !defined(__SPU__)
	if (!m_pCachePos && (m_eVF == eVF_P3S_C4B_T2S || m_eVF == eVF_P3S_N4B_C4B_T2S))
	{
		m_pCachePos = AllocateMeshData<Vec3>(m_nVerts);
		if (m_pCachePos)
		{
			m_nFrameRequestCachePos = gRenDev->m_RP.m_TI[gRenDev->m_RP.m_nFillThreadID].m_nFrameUpdateID;
			return true;
		}
	}
	return false;
	#endif
}

bool CRenderMesh::CreateCachePos(byte *pSrc, uint32 nStrideSrc, uint nFlags)
{
	PROFILE_FRAME(Mesh_CreateCachePos);
	#if !defined(__SPU__)
	if (m_eVF == eVF_P3S_C4B_T2S || m_eVF == eVF_P3S_N4B_C4B_T2S)
	{
	#ifdef USE_VBIB_PUSH_DOWN
		AUTO_LOCK(m_sResLock);//on USE_VBIB_PUSH_DOWN tick is executed in renderthread
	#endif
		m_nFlagsCachePos = nFlags;
		m_nFrameRequestCachePos = gRenDev->m_RP.m_TI[gRenDev->m_RP.m_nFillThreadID].m_nFrameUpdateID;
		if ((nFlags & FSL_READ) && m_pCachePos)
			return true;
		if ((nFlags == FSL_SYSTEM_CREATE) && m_pCachePos)
			return true;
		if (!m_pCachePos)
			m_pCachePos = AllocateMeshData<Vec3>(m_nVerts);
		if (m_pCachePos)
		{
			if (nFlags == FSL_SYSTEM_UPDATE || (nFlags & FSL_READ))
			{
				for (uint32 i=0; i<m_nVerts; i++)
				{
					Vec3f16 *pVSrc = (Vec3f16 *)pSrc;
					m_pCachePos[i] = pVSrc->ToVec3();
					pSrc += nStrideSrc;
				}
			}
			return true;
		}
	}
	return false;
	#endif
}
#endif

byte *CRenderMesh::GetPosPtr(int32& nStride, uint32 nFlags, int32 nOffset)
{
	PROFILE_FRAME(Mesh_GetPosPtr);
	int nStr = 0;
	byte *pData = NULL;
#ifdef KEEP_POSITIONS_INDICES
	if ((nFlags & FSL_READ) && m_pSysPosData)
	{
		pData = (byte *)m_pSysPosData;
#ifdef FP16_MESH
		if (CreateCachePos(pData, sizeof(Vec3f16), nFlags))
			pData = (byte *)m_pCachePos;
#endif
		if (pData)
		{
			nStride = sizeof(Vec3);
			return pData;
		}
	}
#endif
	pData = (byte *)LockVB(VSF_GENERAL, nFlags, nOffset, 0, &nStr, true, true);
	ASSERT_LOCK;
	if (!pData)
		return NULL;
#ifdef FP16_MESH
	if (!CreateCachePos(pData, nStr, nFlags))
	{
		nStride = nStr;
		return pData;
	}
	pData = (byte *)m_pCachePos;
	nStride = sizeof(Vec3);
	return pData;
#else
	nStride = nStr;
	return pData;
#endif
}

uint16 *CRenderMesh::GetIndexPtr(uint32 nFlags, int32 nOffset)
{
	uint16 *pData = LockIB(nFlags, nOffset, 0);
	assert((m_nInds == 0) || pData);
	return pData;
}

#ifndef __SPU__
byte *CRenderMesh::GetColorPtr(int32& nStride, uint32 nFlags, int32 nOffset)
{
	PROFILE_FRAME(Mesh_GetColorPtr);
	int nStr = 0;
	byte *pData = (byte *)LockVB(VSF_GENERAL, nFlags, nOffset, 0, &nStr);
	ASSERT_LOCK;
	if (!pData)
		return NULL;
	nStride = nStr;
	SBufInfoTable *pOffs = &m_cBufInfoTable[_GetVertexFormat()];
	if (pOffs->OffsColor)
		return &pData[pOffs->OffsColor];
	return NULL;
}

byte *CRenderMesh::GetNormPtr(int32& nStride, uint32 nFlags, int32 nOffset)
{
	PROFILE_FRAME(Mesh_GetNormPtr);
	int nStr = 0;
	byte *pData = 0;
	# if ENABLE_NORMALSTREAM_SUPPORT
	pData = (byte *)LockVB(VSF_NORMALS, nFlags, nOffset, 0, &nStr);
	if (pData)
	{
		nStride = sizeof(Vec3);
		return pData;
	}
	# endif
	pData = (byte *)LockVB(VSF_GENERAL, nFlags, nOffset, 0, &nStr);
	ASSERT_LOCK;
	if (!pData)
		return NULL;
	nStride = nStr;
	//assert(_GetVertexFormat() == eVF_P3S_N4B_C4B_T2S);
	if(_GetVertexFormat() == eVF_P3S_N4B_C4B_T2S)
		return pData + sizeof(Vec3f16);
	if(_GetVertexFormat() == eVF_P2S_N4B_C4B_T1F)
		return pData + sizeof(Vec2f16);
	return NULL;
}

byte *CRenderMesh::GetUVPtr(int32& nStride, uint32 nFlags, int32 nOffset)
{
	PROFILE_FRAME(Mesh_GetUVPtr);
	int nStr = 0;
	byte *pData = (byte *)LockVB(VSF_GENERAL, nFlags, nOffset, 0, &nStr);
	ASSERT_LOCK;
	if (!pData)
		return NULL;
	nStride = nStr;
	SBufInfoTable *pOffs = &m_cBufInfoTable[_GetVertexFormat()];
	if (pOffs->OffsTC)
		return &pData[pOffs->OffsTC];
	return NULL;
}
#endif

byte *CRenderMesh::GetTangentPtr(int32& nStride, uint32 nFlags, int32 nOffset)
{
	PROFILE_FRAME(Mesh_GetTangentPtr);
	int nStr = 0;
	byte *pData = (byte *)LockVB(VSF_TANGENTS, nFlags, nOffset, 0, &nStr);
	//ASSERT_LOCK;
	if (!pData)
		pData = (byte *)LockVB(VSF_QTANGENTS, nFlags, nOffset, 0, &nStr);
	if (!pData)
		return NULL;
	nStride = nStr;
	return pData;
}
byte *CRenderMesh::GetBinormalPtr(int32& nStride, uint32 nFlags, int32 nOffset)
{
	PROFILE_FRAME(Mesh_GetBinormalPtr);
	int nStr = 0;
	byte *pData = (byte *)LockVB(VSF_TANGENTS, nFlags, nOffset, 0, &nStr);
	//ASSERT_LOCK;
	if (!pData)
		return NULL;
	nStride = nStr;
	return &pData[sizeof(Vec4sf)];
}

byte *CRenderMesh::GetHWSkinPtr(int32& nStride, uint32 nFlags, int32 nOffset)
{
	PROFILE_FRAME(Mesh_GetHWSkinPtr);
	int nStr = 0;
	byte *pData = (byte *)LockVB(VSF_HWSKIN_INFO, nFlags, nOffset, 0, &nStr);
	ASSERT_LOCK;
	if (!pData)
		return NULL;
	nStride = nStr;
	return pData;
}

#if !defined(__SPU__)
byte *CRenderMesh::GetShapePtr(int32& nStride, uint32 nFlags, int32 nOffset)
{
	PROFILE_FRAME(Mesh_GetShapePtr);
#if !defined(XENON) && !defined(PS3) 
	int nStr = 0;
	byte *pData = (byte *)LockVB(VSF_HWSKIN_SHAPEDEFORM_INFO, nFlags, nOffset, 0, &nStr);
	ASSERT_LOCK;
	if (!pData)
		return NULL;
	nStride = nStr;

#endif
	return pData;
}

byte *CRenderMesh::GetMorphTargetPtr(int32& nStride, uint32 nFlags, int32 nOffset)
{
	PROFILE_FRAME(Mesh_GetMorphTargetPtr);
	int nStr = 0;
	byte *pData = (byte *)LockVB(VSF_HWSKIN_MORPHTARGET_INFO, nFlags, nOffset, 0, &nStr);
	ASSERT_LOCK;
	if (!pData)
		return NULL;
	nStride = nStr;
	return pData;
}

bool CRenderMesh::IsEmpty()
{
	ASSERT_IS_MAIN_THREAD(gRenDev->m_pRT)
	return (!m_nVerts || (!_HasVBStream(VSF_GENERAL) && !m_VBStream[VSF_GENERAL].m_pUpdateData) || (!_HasIBStream() && !m_IBStream.m_pUpdateData));
}

//================================================================================================================

bool CRenderMesh::CheckUpdate(EVertexFormat eVF, uint32 nStreamMask)
{
	CRenderMesh *pRM = _GetVertexContainer();
	if ( pRM ) 
		return gRenDev->m_pRT->RC_CheckUpdate2(this, pRM, eVF, nStreamMask);
	return false; 
}

void CRenderMesh::RT_AllocationFailure()
{
	AUTO_LOCK(m_sResLock);
	Cleanup();
	m_nVerts = 0;
	m_nInds = 0;
	m_nFlags |= FRM_ALLOCFAILURE;
}


#ifdef TESSELLATION_RENDERER
struct half3
{
	inline half3()
	{}
	inline half3(CryHalf _x, CryHalf _y, CryHalf _z)
	{ 
		x = _x; 
		y = _y; 
		z = _z; 
	}

	inline bool operator <(const half3 &v) const
	{
		if (x < v.x) 
			return true;
		if (x > v.x) 
			return false;
		if (y < v.y) 
			return true;
		if (y > v.y) 
			return false;
		return z < v.z;
	}

	inline bool operator ==(const half3 &v) const
	{ 
		return x == v.x && y == v.y && z == v.z; 
	}

	inline bool operator !=(const half3 &v) const
	{ 
		return x != v.x || y != v.y || z != v.z; 
	}

	CryHalf x, y, z;
};

static inline half3 GetVertex(SVF_P3S_C4B_T2S *pVerts, int iVert)
{
	Vec3f16 vert = pVerts[iVert].xyz;
	return half3(CryConvertFloatToHalf(vert.x), CryConvertFloatToHalf(vert.y), CryConvertFloatToHalf(vert.z));
}

struct MY_COMPARE
{
	inline bool operator()(const int i1, const int i2) const
	{
		half3 pVert1 = GetVertex(m_pVerts, i1);
		half3 pVert2 = GetVertex(m_pVerts, i2);
		return pVert1 < pVert2;
	}
	SVF_P3S_C4B_T2S *m_pVerts;
};

static void SaveObj(SVF_P3S_C4B_T2S *pVerts, int nVerts, uint16 *pTrgs, int nTrgs)
{
	if (nTrgs < 50) // don't save too small objects (to throw away unimportant stuff)
		return;
	char fname[100];
	static int iFile = 0;
	sprintf(fname, "c:\\debugObj\\%d.obj", iFile++);
	FILE *fp = fopen(fname, "wt");
	fprintf(fp, "#nverts = %d\n", nVerts);
	fprintf(fp, "#ntrgs = %d\n", nTrgs);
	for (int iv = 0; iv < nVerts; ++iv)
	{
		half3 pVert = GetVertex(pVerts, iv);
		fprintf(fp, "v %f, %f, %f\n", CryConvertHalfToFloat(pVert.x), CryConvertHalfToFloat(pVert.y), CryConvertHalfToFloat(pVert.z));
	}
	for (int it = 0; it < nTrgs; ++it)
	{
		uint16 *pTrg = &pTrgs[it * 12];
		fprintf(fp, "f %d, %d, %d\n", ((int)pTrg[0]) + 1, ((int)pTrg[1]) + 1, ((int)pTrg[2]) + 1);
	}
	fclose(fp);
}

void CRenderMesh::BuildAdjacency(SVF_P3S_C4B_T2S *pVerts, int nVerts, uint16 *pIndexBuffer, int nTrgs, std::vector<Vec2f16> &pTxtAdjBuffer)
{
	// this array will contain indices of vertices sorted by float3 position - so that we could find vertices with equal positions (they have to be adjacent in the array)
	std::vector<int> iSortedVerts;
	// we allocate a bit more (by one) because we will need extra element for scan operation (lower)
	iSortedVerts.resize(nVerts + 1);
	int *_iSortedVerts = &iSortedVerts[0];
	for (int iv = 0; iv < nVerts; ++iv)
	{
		iSortedVerts[iv] = iv;
	}
	MY_COMPARE compare;
	compare.m_pVerts = pVerts;
	std::sort(iSortedVerts.begin(), iSortedVerts.end() - 1, compare);

	// compute how many unique vertices are there, also setup links from each vertex to master vertex
	std::vector<int> iLinkToMaster;
	iLinkToMaster.resize(nVerts);
	int nUniqueVertices = 0;
	for (int iv0 = 0; iv0 < nVerts; )
	{
		int iMaster = iSortedVerts[iv0];
		half3 vMasterVertex = GetVertex(pVerts, iMaster);
		iLinkToMaster[iMaster] = iMaster;
		int iv1 = iv0 + 1;
		for ( ; iv1 < nVerts; ++iv1)
		{
			half3 vSlaveVertex = GetVertex(pVerts, iSortedVerts[iv1]);
			if (vSlaveVertex != vMasterVertex)
			{
				break;
			}
			iLinkToMaster[iSortedVerts[iv1]] = iMaster;
		}
		iv0 = iv1;
		++nUniqueVertices;
	}
	if (nUniqueVertices == nVerts)
	{ // no need to recode anything - the mesh is perfect
		return;
	}
	// compute how many triangles connect to every master vertex
	std::vector<int> &nConnectedTriangles = iSortedVerts;
	for (int i = 0; i < nConnectedTriangles.size(); ++i)
		nConnectedTriangles[i] = 0;
	int *_nConnectedTriangles = &nConnectedTriangles[0];
	for (int it = 0; it < nTrgs; ++it)
	{
		uint16 *pTrg = &pIndexBuffer[it * 3];
		int iMasterVertex0 = iLinkToMaster[pTrg[0]];
		int iMasterVertex1 = iLinkToMaster[pTrg[1]];
		int iMasterVertex2 = iLinkToMaster[pTrg[2]];
		if (iMasterVertex0 == iMasterVertex1 || iMasterVertex0 == iMasterVertex2 || iMasterVertex1 == iMasterVertex2)
			continue; // degenerate triangle - skip it
		++nConnectedTriangles[iMasterVertex0];
		++nConnectedTriangles[iMasterVertex1];
		++nConnectedTriangles[iMasterVertex2];
	}
	// scan
	std::vector<int> &iFirstConnectedTriangle = iSortedVerts;
	for (int iv = 0; iv < nVerts; ++iv)
	{
		iFirstConnectedTriangle[iv + 1] += iFirstConnectedTriangle[iv];
	}
	{
		int iTmp = iFirstConnectedTriangle[0];
		iFirstConnectedTriangle[0] = 0;
		for (int iv = 0; iv < nVerts; ++iv)
		{
			int iTmp1 = iFirstConnectedTriangle[iv + 1];
			iFirstConnectedTriangle[iv + 1] = iTmp;
			iTmp = iTmp1;
		}
	}
	// create a list of triangles for each master vertex
	std::vector<int> pConnectedTriangles;
	pConnectedTriangles.resize(iFirstConnectedTriangle[nVerts]);
	int *_pConnectedTriangles = &pConnectedTriangles[0];
	for (int it = 0; it < nTrgs; ++it)
	{
		uint16 *pTrg = &pIndexBuffer[it * 3];
		int iMasterVertex0 = iLinkToMaster[pTrg[0]];
		int iMasterVertex1 = iLinkToMaster[pTrg[1]];
		int iMasterVertex2 = iLinkToMaster[pTrg[2]];
		if (iMasterVertex0 == iMasterVertex1 || iMasterVertex0 == iMasterVertex2 || iMasterVertex1 == iMasterVertex2)
			continue; // degenerate triangle - skip it
		pConnectedTriangles[iFirstConnectedTriangle[iMasterVertex0]++] = it;
		pConnectedTriangles[iFirstConnectedTriangle[iMasterVertex1]++] = it;
		pConnectedTriangles[iFirstConnectedTriangle[iMasterVertex2]++] = it;
	}
	// return scan array to initial state
	{
		int iTmp = iFirstConnectedTriangle[0];
		iFirstConnectedTriangle[0] = 0;
		for (int iv = 0; iv < nVerts; ++iv)
		{
			int iTmp1 = iFirstConnectedTriangle[iv + 1];
			iFirstConnectedTriangle[iv + 1] = iTmp;
			iTmp = iTmp1;
		}
	}
	// find matches for boundary edges
	for (int it = 0; it < nTrgs; ++it)
	{
		uint16 *pTrg = &pIndexBuffer[it * 3];
		for (int ie = 0; ie < 3; ++ie)
		{
			// fix the corner here
			{
				int ivCorner = pTrg[ie];
				pTxtAdjBuffer[it * 12 + 9 + ie] = pVerts[iLinkToMaster[ivCorner]].st;
			}
			// proceed with fixing the edges
			int iv0 = pTrg[ie];
			int iMasterVertex0 = iLinkToMaster[iv0];

			int iv1 = pTrg[(ie + 1) % 3];
			int iMasterVertex1 = iLinkToMaster[iv1];
			if (iMasterVertex0 == iMasterVertex1)
			{ // some degenerate case - skip it
				continue;
			}
			// find a triangle that has both iMasterVertex0 and iMasterVertex1
			for (int i0 = iFirstConnectedTriangle[iMasterVertex0]; i0 < iFirstConnectedTriangle[iMasterVertex0 + 1]; ++i0)
			{
				int iOtherTriangle = pConnectedTriangles[i0];
				if (iOtherTriangle >= it) // we are going to stick to this other triangle only if it's index is less than ours
					continue;
				uint16 *pOtherTrg = &pIndexBuffer[iOtherTriangle * 3];
				int iRecode0 = -1;
				int iRecode1 = -1;
				// setup recode indices
				for (int ieOther = 0; ieOther < 3; ++ieOther)
				{
					if (iLinkToMaster[pOtherTrg[ieOther]] == iMasterVertex0)
					{
						iRecode0 = pOtherTrg[ieOther];
					}
					else if (iLinkToMaster[pOtherTrg[ieOther]] == iMasterVertex1)
					{
						iRecode1 = pOtherTrg[ieOther];
					}
				}
				if (iRecode0 != -1 && iRecode1 != -1)
				{ // this triangle is our neighbor
					pTxtAdjBuffer[it * 12 + 3 + ie * 2] = pVerts[iRecode0].st;
					pTxtAdjBuffer[it * 12 + 3 + ie * 2 + 1] = pVerts[iRecode1].st;
				}
			}
		}
	}
}
#endif //#ifdef TESSELLATION_RENDERER

bool CRenderMesh::RT_CheckUpdate(CRenderMesh *pVContainer, EVertexFormat eVF, uint32 nStreamMask, bool bTessellation, bool stall)
{
	PrefetchLine(&m_IBStream, 0);

	CRenderer *rd = gRenDev;
	int nThreadID = rd->m_RP.m_nProcessThreadID;
	int nFrame = rd->m_RP.m_TI[nThreadID].m_nFrameUpdateID;
	bool bSkinned = (m_nFlags & (FRM_SKINNED | FRM_SKINNEDNEXTDRAW)) != 0;

	#if !defined(XENON) && !defined(PS3)
	if (nStreamMask & 0x80000000) // Disable skinning in instancing mode
		bSkinned = false;
	#endif

	m_nFlags &= ~FRM_SKINNEDNEXTDRAW;

	IF (!CanRender(), 0)
		return false;

	FUNCTION_PROFILER_RENDER_FLAT

	//  assert (m_pVertexContainer || m_nVerts > 2);
	PrefetchLine(pVContainer->m_VBStream, 0);

	if (m_pVertexContainer || m_nVerts > 2)
	{
		PrefetchLine(pVContainer->m_VBStream, 128);
		#if defined(_DEBUG) && !defined(NULL_RENDERER)
		if (pVContainer->m_VBStream[VSF_GENERAL].m_DevBuf.m_nID >= 0)
		{
			int32 nOffset = 0;
			void *pBuf = gRenDev->m_DevBufMan.GetD3DVB(pVContainer->m_VBStream[VSF_GENERAL].m_DevBuf.m_nID, &nOffset);
			assert(pVContainer->m_VBStream[VSF_GENERAL].m_DevBuf.m_pDevBuf == pBuf && pVContainer->m_VBStream[VSF_GENERAL].m_DevBuf.m_nOffset == nOffset);
		}
#endif
		if (pVContainer->m_VBStream[VSF_GENERAL].m_pUpdateData && pVContainer->m_VBStream[VSF_GENERAL].m_nFrameAccess != nFrame)
		{
			pVContainer->m_VBStream[VSF_GENERAL].m_nFrameAccess = nFrame;
			if (pVContainer->m_VBStream[VSF_GENERAL].m_nFrameRequest > pVContainer->m_VBStream[VSF_GENERAL].m_nFrameUpdate)
			{
				{
					PROFILE_FRAME(Mesh_CheckUpdateUpdateGBuf);
					if (!(pVContainer->m_VBStream[VSF_GENERAL].m_nLockFlags & FSL_WRITE))
					{
								if (!pVContainer->UpdateVidVertices(VSF_GENERAL, stall))
								{
									RT_AllocationFailure();
									return false;
								}
								pVContainer->m_VBStream[VSF_GENERAL].m_nFrameUpdate = nFrame;
					}
					else
					{
								if (pVContainer->m_VBStream[VSF_GENERAL].m_DevBuf.m_pDevBuf == 0)
									return false;
					}
				}
			}
		}

// Set both tangent flags
		if (nStreamMask & VSM_TANGENTS)
			nStreamMask |= VSM_TANGENTS;

		// Additional streams updating
		if (nStreamMask & VSM_MASK)
		{
			int i;
			uint32 iMask = 1;
	    
			for (i=1; i<VSF_NUM; i++)
			{
				iMask = iMask << 1;

				if (nStreamMask & iMask)
				{
#if defined(_DEBUG) && !defined(NULL_RENDERER)
					if (pVContainer->m_VBStream[i].m_DevBuf.m_nID >= 0)
					{
						int32 nOffset = 0;
						void *pBuf = gRenDev->m_DevBufMan.GetD3DVB(pVContainer->m_VBStream[i].m_DevBuf.m_nID, &nOffset);
						assert(pVContainer->m_VBStream[i].m_DevBuf.m_pDevBuf == pBuf && pVContainer->m_VBStream[i].m_DevBuf.m_nOffset == nOffset);
					}
#endif
					if (pVContainer->m_VBStream[i].m_pUpdateData && pVContainer->m_VBStream[i].m_nFrameAccess != nFrame)
					{
						pVContainer->m_VBStream[i].m_nFrameAccess = nFrame;
						if (pVContainer->m_VBStream[i].m_nFrameRequest > pVContainer->m_VBStream[i].m_nFrameUpdate)
						{
										// Update the device buffer
							PROFILE_FRAME(Mesh_CheckUpdateUpdateGBuf);
							if (!(pVContainer->m_VBStream[i].m_nLockFlags & FSL_WRITE))
							{
								if (!pVContainer->UpdateVidVertices(i, stall))
								{
									RT_AllocationFailure();
									return false;
								}
								pVContainer->m_VBStream[i].m_nFrameUpdate = nFrame;
							}
							else
							if (i != VSF_HWSKIN_INFO)
							{
								int nnn = 0;
								if (pVContainer->m_VBStream[i].m_DevBuf.m_pDevBuf == 0)
									return false;
							}
						}
					}
				}
			}
		}
	}//if (m_pVertexContainer || m_nVerts > 2)

	const bool bIndUpdateNeeded = (m_IBStream.m_pUpdateData != NULL) && (m_IBStream.m_nFrameRequest > m_IBStream.m_nFrameUpdate);

	//if (m_IBStream.m_nFrameAccess != nFrame)
	{
		m_IBStream.m_nFrameAccess = nFrame;
		#if defined(_DEBUG) && !defined(NULL_RENDERER)
		if (m_IBStream.m_DevBuf.m_nID >= 0)
		{
			int32 nOffset = 0;
			void *pBuf = gRenDev->m_DevBufMan.GetD3DIB(m_IBStream.m_DevBuf.m_nID, &nOffset);
			assert(m_IBStream.m_DevBuf.m_pDevBuf == pBuf && m_IBStream.m_DevBuf.m_nOffset == nOffset);
		}
		#endif
		if (bIndUpdateNeeded)
		{
			PROFILE_FRAME(Mesh_CheckUpdate_UpdateInds);
			if (!(pVContainer->m_IBStream.m_nLockFlags & FSL_WRITE))
			{
				if (!UpdateVidIndices(m_IBStream, stall))
				{
					RT_AllocationFailure();
					return false;
				}
				m_IBStream.m_nFrameUpdate = nFrame;
			}
			else if (pVContainer->m_IBStream.m_DevBuf.m_pDevBuf == 0)
				return false;
		}
	}

#ifdef TESSELLATION_RENDERER
	// Build UV adjacency
	if ((bTessellation && m_adjBuffer.m_numElements == 0)       // if needed and not built already
		|| (bIndUpdateNeeded && m_adjBuffer.m_numElements > 0)) // if already built but needs update
	{
		if (!(pVContainer->m_IBStream.m_nLockFlags & FSL_WRITE)
			&& (pVContainer->_HasVBStream(VSF_NORMALS)))
		{
			UpdateUVCoordsAdjacency(m_IBStream);
		}
	}
#endif

	return true;
}

void CRenderMesh::ReleaseVB(int nStream)
{
	UnlockVB(nStream);
	gRenDev->m_pRT->RC_ReleaseVB(m_VBStream[nStream].m_DevBuf.m_nID);
	m_VBStream[nStream].m_nElements = 0;
	m_VBStream[nStream].m_DevBuf.m_pDevBuf = NULL;
	m_VBStream[nStream].m_DevBuf.m_nID = -1;
	m_VBStream[nStream].m_DevBuf.m_nOffset = 0;
	m_VBStream[nStream].m_nFrameUpdate = -1;
	m_VBStream[nStream].m_nFrameCreate = -1;
}

void CRenderMesh::ReleaseIB()
{
	UnlockIB();
	gRenDev->m_pRT->RC_ReleaseIB(m_IBStream.m_DevBuf.m_nID);
	m_IBStream.m_nElements = 0;
	m_IBStream.m_DevBuf.m_pDevBuf = NULL;
	m_IBStream.m_DevBuf.m_nID = -1;
	m_IBStream.m_DevBuf.m_nOffset = 0;
	m_IBStream.m_nFrameUpdate = -1;
	m_IBStream.m_nFrameCreate = -1;
}

bool CRenderMesh::UpdateIndices_Int(const uint16 *pNewInds, int nInds, int nOffsInd, uint32 copyFlags)
{
	MEMSTAT_CONTEXT(EMemStatContextTypes::MSC_RenderMeshType, 0, this->GetTypeName());
	MEMSTAT_CONTEXT(EMemStatContextTypes::MSC_RenderMesh, 0, this->GetSourceName());

	//LockVB operates now on a per mesh lock, any thread may access
	//ASSERT_IS_MAIN_THREAD(gRenDev->m_pRT)

	//AUTO_LOCK(m_sResLock);

	// Resize the index buffer
	if (m_nInds != nInds)
	{
		FreeIB();
		m_nInds = nInds;
	}
	if (!nInds)
	{
		assert(!m_IBStream.m_pUpdateData);
		return true;
	}

	uint16 *pDst = LockIB(FSL_VIDEO_CREATE, 0, nInds);
	if (pDst && pNewInds)
	{
		if (copyFlags&FSL_ASYNC_DEFER_COPY && nInds*sizeof(uint16)<RENDERMESH_ASYNC_MEMCPY_THRESHOLD)
			cryAsyncMemcpy(
			&pDst[nOffsInd], 
			pNewInds, 
			nInds*sizeof(uint16),
			MC_CPU_TO_GPU|copyFlags, 
			SetAsyncUpdateState());
		else
		{
			cryMemcpy(
				&pDst[nOffsInd], 
				pNewInds, 
				nInds*sizeof(uint16),
				MC_CPU_TO_GPU);
			UnlockIndexStream();
		}
	}
	else
		return false;

	return true;
}

bool CRenderMesh::UpdateVertices_Int(const void *pVertBuffer, int nVertCount, int nOffset, int nStream, uint32 copyFlags)
{
	//LockVB operates now on a per mesh lock, any thread may access
	//  ASSERT_IS_MAIN_THREAD(gRenDev->m_pRT)

	int nStride;

	//AUTO_LOCK(m_sResLock);

	// Resize the vertex buffer
	if (m_nVerts != nVertCount)
	{
		for (int i=0; i<VSF_NUM; i++)
		{
			FreeVB(i);
		}
		m_nVerts = nVertCount;
	}

	if (!m_nVerts)
		return true;

	byte *pDstVB = (byte *)LockVB(nStream, FSL_VIDEO_CREATE, 0, nVertCount, &nStride);
	assert((nVertCount == 0) || pDstVB);
	if (pDstVB && pVertBuffer)
	{
		if (copyFlags&FSL_ASYNC_DEFER_COPY && nStride*nVertCount<RENDERMESH_ASYNC_MEMCPY_THRESHOLD)
		{
			cryAsyncMemcpy(
				&pDstVB[nOffset], 
				pVertBuffer, 
				nStride*nVertCount,
				MC_CPU_TO_GPU|copyFlags, 
				SetAsyncUpdateState());
		}
		else
		{
			cryMemcpy(
				&pDstVB[nOffset], 
				pVertBuffer, 
				nStride*nVertCount,
				MC_CPU_TO_GPU);
			UnlockStream(nStream);
		}
	}
	else
		return false;

	return true;
}

bool CRenderMesh::UpdateVertices(
	const void *pVertBuffer
	, int nVertCount
	, int nOffset
	, int nStream
	, uint32 copyFlags
	, bool requiresLock)
{
	bool result = false; 
	if(requiresLock)
	{
		AUTO_LOCK(m_sResLock);
		result = UpdateVertices_Int(pVertBuffer, nVertCount, nOffset, nStream, copyFlags);
	}
	else
	{
		result = UpdateVertices_Int(pVertBuffer, nVertCount, nOffset, nStream, copyFlags);
	}
	return result;
}

bool CRenderMesh::UpdateIndices(
	const uint16 *pNewInds
	, int nInds
	, int nOffsInd
	, uint32 copyFlags
	, bool requiresLock)
{
	bool result = false; 
	if(requiresLock)
	{
		AUTO_LOCK(m_sResLock);
		result = UpdateIndices_Int(pNewInds, nInds, nOffsInd, copyFlags);
	}
	else
	{
		result = UpdateIndices_Int(pNewInds, nInds, nOffsInd, copyFlags);
	}
	return result;
}

bool CRenderMesh::UpdateVidIndices(SMeshStream& IBStream, bool stall)
{
	MEMSTAT_CONTEXT(EMemStatContextTypes::MSC_RenderMeshType, 0, this->GetTypeName());
	MEMSTAT_CONTEXT(EMemStatContextTypes::MSC_RenderMesh, 0, this->GetSourceName());
	SCOPED_RENDERER_ALLOCATION_NAME_HINT( GetSourceName() );

	assert(gRenDev->m_pRT->IsRenderThread());

	AUTO_LOCK(m_sResLock);

	assert(gRenDev->m_pRT->IsRenderThread());

	int nInds = m_nInds;
	if (m_nFlags & FRM_INSTANCED)
	nInds *= RM_INSTANCES;

	if (!nInds)
	{
		// 0 size index buffer creation crashes on 360
		assert( nInds );
		return false;
	}

	if (IBStream.m_nElements != m_nInds && _HasIBStream())
	ReleaseIB();

	if (IBStream.m_DevBuf.m_nID < 0)
	{
		IBStream.m_DevBuf.m_nID = gRenDev->m_DevBufMan.CreateIB(nInds*sizeof(uint16));		
#ifndef NULL_RENDERER
		IBStream.m_DevBuf.m_pDevBuf = gRenDev->m_DevBufMan.GetD3DIB(IBStream.m_DevBuf.m_nID, &IBStream.m_DevBuf.m_nOffset);
#endif
		IBStream.m_nElements = m_nInds;
		IBStream.m_nFrameCreate =  gRenDev->m_RP.m_TI[gRenDev->m_pRT->IsMainThread() 	? gRenDev->m_RP.m_nFillThreadID : gRenDev->m_RP.m_nProcessThreadID].m_nFrameUpdateID;
	}

	if (IBStream.m_DevBuf.m_nID >= 0)
	{
		UnlockIndexStream();
		if (m_IBStream.m_pUpdateData)
		{
			/* fprintf(stderr, "RM: 0x%8x uploading ib 0x%8xto rsx\n", this, m_IBStream.m_pUpdateData); */
			bool bRes = true;
			int n = (m_nFlags & FRM_INSTANCED) ? RM_INSTANCES : 1;
			int nOffs = 0;
			uint16 *pTemp = NULL;
			std::vector<Vec2f16> pTxtAdjBuffer;
			for (int i=0; i<n; i++)
			{
				if (nOffs)
				{
					if (!pTemp)
						pTemp = reinterpret_cast<uint16*>(AllocateMeshData(m_nInds));
					if (pTemp)
					{
						uint16 *pSrc = (uint16 *)m_IBStream.m_pUpdateData;
						int nV = m_nVerts*i;
						for (uint32 j=0; j<m_nInds; j++)
						{
							pTemp[j] = pSrc[j] + nV;
						}
						if (!gRenDev->m_DevBufMan.UpdateIB(IBStream.m_DevBuf.m_nID, pTemp, m_nInds*sizeof(uint16), nOffs*sizeof(uint16), stall))
							bRes = false;
					}
				}
				else
				{
#if defined(USE_VBIB_PUSH_DOWN)
					if (!(gRenDev->m_DevBufMan.UpdateIB(IBStream.m_DevBuf.m_nID, IBStream.m_pUpdateData, m_nInds*sizeof(uint16), 0, stall))) 
						bRes = false;
#else
					IBStream.m_pLockedData = gRenDev->m_DevBufMan.UpdateIB(IBStream.m_DevBuf.m_nID, IBStream.m_pUpdateData, m_nInds*sizeof(uint16), 0, stall);
					if (!IBStream.m_pLockedData)
						bRes = false;
#endif
				}
				nOffs += m_nInds;
			}

		#ifndef NULL_RENDERER
			IBStream.m_DevBuf.m_pDevBuf = gRenDev->m_DevBufMan.GetD3DIB(IBStream.m_DevBuf.m_nID, &IBStream.m_DevBuf.m_nOffset);
		#endif
			FreeMeshData(pTemp);
			return bRes;
		}
	}
	return false;
}

bool CRenderMesh::CreateVidVertices(int nVerts, EVertexFormat eVF, int nStream)
{
	MEMSTAT_CONTEXT(EMemStatContextTypes::MSC_RenderMeshType, 0, this->GetTypeName());
	MEMSTAT_CONTEXT(EMemStatContextTypes::MSC_RenderMesh, 0, this->GetSourceName());
	SCOPED_RENDERER_ALLOCATION_NAME_HINT( GetSourceName() );

	AUTO_LOCK(m_sResLock);

	if (gRenDev->m_bDeviceLost)
		return false;
	if (!nVerts && eVF==eVF_Unknown)
	{
		nVerts = m_nVerts;
		eVF = m_eVF;
	}

	if (m_nFlags & FRM_INSTANCED)
		nVerts *= RM_INSTANCES;

	assert (!_HasVBStream(nStream));

	int nSize = GetStreamSize(nStream, nVerts);
	m_VBStream[nStream].m_DevBuf.m_nID = gRenDev->m_DevBufMan.CreateVB(nSize);
#ifndef NULL_RENDERER
	m_VBStream[nStream].m_DevBuf.m_pDevBuf = gRenDev->m_DevBufMan.GetD3DVB(
		m_VBStream[nStream].m_DevBuf.m_nID, &m_VBStream[nStream].m_DevBuf.m_nOffset);

#endif
	assert(m_VBStream[nStream].m_DevBuf.m_pDevBuf);
	m_VBStream[nStream].m_nElements = nVerts;
	m_VBStream[nStream].m_nFrameCreate = 
		gRenDev->m_RP.m_TI[gRenDev->m_pRT->IsMainThread() ? gRenDev->m_RP.m_nFillThreadID : gRenDev->m_RP.m_nProcessThreadID].m_nFrameUpdateID;
	return (m_VBStream[nStream].m_DevBuf.m_nID >= 0);
}

bool CRenderMesh::UpdateVidVertices(int nStream, bool stall)
{
	//MEMSTAT_CONTEXT(EMemStatContextTypes::MSC_RenderMeshType, 0, this->GetTypeName());
	//MEMSTAT_CONTEXT(EMemStatContextTypes::MSC_RenderMesh, 0, this->GetSourceName());

	assert(gRenDev->m_pRT->IsRenderThread());

	AUTO_LOCK(m_sResLock);

	assert(nStream < VSF_NUM);

	if (m_nVerts != m_VBStream[nStream].m_nElements && _HasVBStream(nStream))
		ReleaseVB(nStream);

	if (m_VBStream[nStream].m_DevBuf.m_nID<0)
	{
		if (!CreateVidVertices(m_nVerts, m_eVF, nStream))
			return false;
	}

	if (m_VBStream[nStream].m_DevBuf.m_nID>=0)
	{
		UnlockStream(nStream);
		if (m_VBStream[nStream].m_pUpdateData)
		{
			int nSStride = GetStreamStride(nStream);
			int nSSize = GetStreamSize(nStream);

			bool bRes = true;
			int n = (m_nFlags & FRM_INSTANCED) ? RM_INSTANCES : 1;
			int nOffs = 0;
			byte *pTemp = NULL;
			for (int i=0; i<n; i++)
			{
#ifdef FP16_MESH
				if (n > 1)
				{
					if (!pTemp)
						pTemp = reinterpret_cast<byte*>(AllocateMeshData(nSSize));
					if (pTemp)
					{
						byte *pSrc = (byte *)m_VBStream[nStream].m_pUpdateData;
						memcpy(pTemp, pSrc, nSSize);
						if (nStream == VSF_GENERAL)
						{
							byte *pDst = pTemp;
							CryHalf hInd = CryConvertFloatToHalf((float)i + 0.1f);
							for (uint32 j=0; j<m_nVerts; j++)
							{
								Vec3f16 *pVDst = (Vec3f16 *)pDst;
								pVDst->w = hInd;
								pDst += nSStride;
							}
						}
						void *pLocked = gRenDev->m_DevBufMan.UpdateVB(m_VBStream[nStream].m_DevBuf.m_nID, pTemp, nSSize, nOffs*nSStride, stall);
						if (!pLocked)
							bRes = false;
						else
							if (!nOffs)
							{
#if !defined(USE_VBIB_PUSH_DOWN)			
								m_VBStream[nStream].m_pLockedData = pLocked;
#endif
							}
					}
				}
				else
#endif
				{
#if defined(USE_VBIB_PUSH_DOWN)
					if ( !(gRenDev->m_DevBufMan.UpdateVB(m_VBStream[nStream].m_DevBuf.m_nID, m_VBStream[nStream].m_pUpdateData, nSSize, nOffs*nSStride, stall)) )
						bRes = false;
#else
					m_VBStream[nStream].m_pLockedData = gRenDev->m_DevBufMan.UpdateVB(m_VBStream[nStream].m_DevBuf.m_nID, m_VBStream[nStream].m_pUpdateData, nSSize, nOffs*nSStride, stall);
					if (!m_VBStream[nStream].m_pLockedData)
						bRes = false;
#endif
				}
				nOffs += m_nVerts;
			}
#ifndef NULL_RENDERER
			m_VBStream[nStream].m_DevBuf.m_pDevBuf = gRenDev->m_DevBufMan.GetD3DVB(m_VBStream[nStream].m_DevBuf.m_nID, &m_VBStream[nStream].m_DevBuf.m_nOffset);
#endif
			FreeMeshData(pTemp);
			return bRes;
		}
		else
		{
			assert(0);
		}
	}
	return false;
}

#ifdef TESSELLATION_RENDERER
bool CRenderMesh::UpdateUVCoordsAdjacency(SMeshStream& IBStream)
{
	MEMSTAT_CONTEXT(EMemStatContextTypes::MSC_RenderMeshType, 0, this->GetTypeName());
	MEMSTAT_CONTEXT(EMemStatContextTypes::MSC_RenderMesh, 0, this->GetSourceName());
	SCOPED_RENDERER_ALLOCATION_NAME_HINT( GetSourceName() );

	assert(gRenDev->m_pRT->IsRenderThread());

	AUTO_LOCK(m_sResLock);

	int nInds = m_nInds * 4;
	if (m_nFlags & FRM_INSTANCED)
		nInds *= RM_INSTANCES;

	if (!nInds)
	{
		// 0 size index buffer creation crashes on 360
		assert( nInds );
		return false;
	}

	if (IBStream.m_DevBuf.m_nID >= 0)
	{
		if (m_IBStream.m_pUpdateData)
		{
			bool bRes = true;
			std::vector<Vec2f16> pTxtAdjBuffer;

			// create triangles with adjacency
			SVF_P3S_C4B_T2S *pVertexStream = (SVF_P3S_C4B_T2S *)m_VBStream[0].m_pUpdateData;
			if (m_eVF == eVF_P3S_C4B_T2S && pVertexStream)
			{
				int nTrgs = m_nInds / 3;
				pTxtAdjBuffer.resize(nTrgs * 12);
				int nVerts = _GetNumVerts();
				for (int n = 0; n < nTrgs; ++n)
				{
					// fill in the dummy adjacency first
					Vec2f16 *pDst = &pTxtAdjBuffer[n * 12];
					uint16 *pSrc = (uint16 *)m_IBStream.m_pUpdateData + n * 3;
					// triangle itself
					pDst[0] = pVertexStream[pSrc[0]].st;
					pDst[1] = pVertexStream[pSrc[1]].st;
					pDst[2] = pVertexStream[pSrc[2]].st;
					// adjacency by edges
					pDst[3] = pVertexStream[pSrc[0]].st;
					pDst[4] = pVertexStream[pSrc[1]].st;
					pDst[5] = pVertexStream[pSrc[1]].st;
					pDst[6] = pVertexStream[pSrc[2]].st;
					pDst[7] = pVertexStream[pSrc[2]].st;
					pDst[8] = pVertexStream[pSrc[0]].st;
					// adjacency by corners
					pDst[9] = pVertexStream[pSrc[0]].st;
					pDst[10] = pVertexStream[pSrc[1]].st;
					pDst[11] = pVertexStream[pSrc[2]].st;
				}

				// now real adjacency is computed
				BuildAdjacency(pVertexStream, nVerts, (uint16 *)m_IBStream.m_pUpdateData, nTrgs, pTxtAdjBuffer);
		#ifdef FP16_MESH
				m_adjBuffer.Create(pTxtAdjBuffer.size(), sizeof(Vec2f16), DXGI_FORMAT_R16G16_FLOAT, &pTxtAdjBuffer[0], D3D11_BIND_SHADER_RESOURCE);
		#else
				m_adjBuffer.Create(pTxtAdjBuffer.size(), sizeof(Vec2f16), DXGI_FORMAT_R32G32_FLOAT, &pTxtAdjBuffer[0], D3D11_BIND_SHADER_RESOURCE);
		#endif

				// HS needs to know iPatchID offset for each drawcall, so we pass this offset in the constant buffer
				// currently texture buffer is created, but when parser support for cbuffer is added (AI: AndreyK), we
				// need to change the below call to:
				// ((CREMeshImpl*) m_Chunks[iChunk].pRE)->m_tessCB.Create(4, sizeof(int), (DXGI_FORMAT)0, &myBuffer[0], D3D11_BIND_CONSTANT_BUFFER);
				for (int iChunk = 0; iChunk < m_Chunks.size(); ++iChunk)
				{
					int myBuffer[4];
					myBuffer[0] = m_Chunks[iChunk].nFirstIndexId / 3;
					((CREMeshImpl*) m_Chunks[iChunk].pRE)->m_tessCB.Create(4, sizeof(int), DXGI_FORMAT_R32_SINT, &myBuffer[0], D3D11_BIND_SHADER_RESOURCE);
				}
			}

			return bRes;
		}
	}
	return false;
}
#endif //#ifdef TESSELLATION_RENDERER

void CRenderMesh::Render(const struct SRendParams& rParams, CRenderObject* pObj, IMaterial *pMaterial, bool bSkinned)
{
	FUNCTION_PROFILER_FAST(GetISystem(), PROFILE_RENDERER, g_bProfilerEnabled);

	IF(!CanRender(),0)
		return;

	int nList = rParams.nRenderList;
	int nAW = rParams.nAfterWater;
	CRenderer *rd = gRenDev;

#ifndef _RELEASE
	const char *szExcl = CRenderer::CV_r_excludemesh->GetString();
	if (szExcl[0] && m_sSource)
	{
		char szMesh[1024];
		strcpy_s(szMesh, this->m_sSource);
		strlwr(szMesh);
		if (szExcl[0] == '!')
		{
			if (!strstr(&szExcl[1], m_sSource))
				return;
		}
		else
			if (strstr(szExcl, m_sSource))
				return;
	}
#endif

	if (rd->m_pDefaultMaterial && pMaterial)
		pMaterial = rd->m_pDefaultMaterial;

	assert(pMaterial);

	if(!pMaterial)
		return;

	pObj->m_pRenderNode = rParams.pRenderNode;
	pObj->m_pCurrMaterial = pMaterial;
	SRenderObjData *pOD = NULL;

	if (rParams.nHUDSilhouettesParams || rParams.nVisionParams || rParams.pLayerEffectParams || rParams.pInstance)
	{
		if (!pOD)
			pOD = rd->EF_GetObjData(pObj, true);
		pOD->m_pLayerEffectParams = rParams.pLayerEffectParams;
		pOD->m_nVisionParams = rParams.nVisionParams;
		pOD->m_nHUDSilhouetteParams = rParams.nHUDSilhouettesParams;
		pOD->m_pInstance = rParams.pInstance;
	}


	assert(!(pObj->m_ObjFlags & FOB_BENDED));

	bool bSG = (rd->m_RP.m_TI[rd->m_RP.m_nFillThreadID].m_PersFlags & RBPF_SHADOWGEN) != 0;

	if( gRenDev->s_AllowMotionBlur >= 2 && !bSG)
		CMotionBlur::SetupObject(pObj);

	PodArray<CRenderChunk> *pChunks = bSkinned ? m_pChunksSkinned : &m_Chunks;

	if (pChunks)
	{
		const uint32 ni = (uint32)pChunks->Count();
		for (uint32 i=0; i<ni; i++)     
		{
			CRenderChunk * pChunk = pChunks->Get(i);
			CRendElementBase * pREMesh = pChunk->pRE;

			SShaderItem& ShaderItem = pMaterial->GetShaderItem(pChunk->m_nMatID);

			SRenderShaderResources *pR = (SRenderShaderResources *)ShaderItem.m_pShaderResources;
			CShader *pS = (CShader *)ShaderItem.m_pShader;
			if (pREMesh && pS && pR)
			{
				if (pS->m_Flags2 & EF2_NODRAW)
					continue;
			/*
				SShaderItem shaderItem;
				if (rParams.nTechniqueID > 0)
				{
					shaderItem = *pShaderItem;
					shaderItem.m_nTechnique = pS->GetTechniqueID(shaderItem.m_nTechnique, rParams.nTechniqueID);
					pShaderItem = &shaderItem;
				}
			*/
				rd->EF_AddEf_NotVirtual(pREMesh, ShaderItem, pObj, nList, nAW);  
			}
		} 
	}
}

void CRenderMesh::AddHUDRenderElement(CRenderObject * pObj, IMaterial *pMaterial)
{
	EPostEffectID postEffectID = ePFX_3DHUD;
	if( SRenderObjData *pOD = gRenDev->EF_GetObjData(pObj, false) )
	{
		if(pOD->m_nCustomFlags & COB_CUSTOM_POST_EFFECT) 
		{
			postEffectID = (EPostEffectID)pOD->m_nCustomData;
		}
	}
	CPostEffect *pPostEffect = PostEffectMgr()->GetEffect(postEffectID);
	PodArray<CRenderChunk>* pChunks = &m_Chunks;
	const uint32 ni = (uint32)pChunks->Count();

	for (uint32 i=0; i<ni; i++)     
	{
		CRenderChunk* pChunk = pChunks->Get(i);
		CRendElementBase* pREMesh = pChunk->pRE;
		SShaderItem* pShaderItem = &pMaterial->GetShaderItem(pChunk->m_nMatID);

		pPostEffect->AddRE( pREMesh, pShaderItem, pObj );
	}
}

// break-ability support
IRenderMesh * CRenderMesh::GetRenderMeshForSubsetMask(SRenderObjData *pOD, uint64 nMeshSubSetMask, IMaterial * pMaterial)
{
// TODO: If only one bit is set in mask - there is no need to build new index buffer - small part of main index buffer can be re-used
// TODO: Add auto releasing of not used for long time index buffers
// TODO: Take into account those induces when computing render mesh memory size for CGF streaming
// TODO: Support for multiple materials
  
	assert(nMeshSubSetMask);

	IRenderMesh * pSrcRM = this;
	CRenderChunkArray &renderChunks = m_ChunksSubObjects;

	uint32 nChunkCount = renderChunks.size();
	nMeshSubSetMask &= (((uint64)1<<nChunkCount)-1);	

	// update all finished running tasks	
	for( MeshSubSetIndicesTasks::iterator it = m_meshSubSetIndicesTasks.begin() ; it != m_meshSubSetIndicesTasks.end() ;  )
	{
		CMeshSubSetIndicesTask *pTask = it->m_pTask;
		if( pTask->HasFinished() )
		{
			IRenderMesh *pIndexRM = pTask->GetIndexRM();
			uint64 nTaskMeshSubSetMask = pTask->GetMeshSubSetMask();

			// if a render mesh was created, add it to the finished index render meshes
			if( pIndexRM )
			{
				MeshSubSetIndices::iterator subsetIt = m_meshSubSetIndices.insert( std::make_pair(nTaskMeshSubSetMask, pIndexRM ) ).first;

				if (pIndexRM)
				{
					// CreateRenderMesh returns an IRenderMesh with a ref count of 1, the _smart_ptr has now bumped this to 2 - so drop it again to prevent a leak later		
					pIndexRM->Release();				
				}
			}

			SAFE_DELETE(pTask);
			it = m_meshSubSetIndicesTasks.erase(it);
		}
		else
		{
			++it;
		}
	}

	// try to find the index mesh in the already finished list
	const MeshSubSetIndices::iterator meshSubSet = m_meshSubSetIndices.find( nMeshSubSetMask );

	if(meshSubSet != m_meshSubSetIndices.end())
	{
		return meshSubSet->second;
	}
	
	// no finished index mesh found, check if a task is already running for it
	MeshSubSetIndicesTasks::iterator meshTask = m_meshSubSetIndicesTasks.find( CMeshSubSetIndicesTaskEntry(nMeshSubSetMask) );

	if(meshTask != m_meshSubSetIndicesTasks.end())
	{
		// running task found, return last mesh
		return NULL;
	}


	CMeshSubSetIndicesTask *pMeshSubSetIndicesTask = new CMeshSubSetIndicesTask(this, nMeshSubSetMask);
	CMeshSubSetIndicesTask::AddTaskToPool(pMeshSubSetIndicesTask);
	
	m_meshSubSetIndicesTasks.insert( CMeshSubSetIndicesTaskEntry(pMeshSubSetIndicesTask, nMeshSubSetMask) );

	// while computing the new one, use the last used rendermesh
	return NULL;
}

void CRenderMesh::CreateMergedChunks(IMaterial *pMaterial)
{
	// Create merged chunks
	AUTO_LOCK(m_sResLock);
	CRenderChunk ch;
	CRenderChunk *pChunk = &m_Chunks[0];
	ch.nFirstIndexId = pChunk->nFirstIndexId;
	ch.nFirstVertId = pChunk->nFirstVertId;
	uint32 nInds = pChunk->nNumIndices;
	uint32 nVerts = pChunk->nNumVerts;
	SShaderItem& SH = pMaterial->GetShaderItem(pChunk->m_nMatID);
	SRenderShaderResources *pR = (SRenderShaderResources *)SH.m_pShaderResources;
	CShader *pSH = (CShader *)SH.m_pShader;
	bool bAT = pR->GetAlphaRef() > 0.0f;
	uint32 iNoShadow = (pR->m_ResFlags & MTL_FLAG_NOSHADOW) || (pSH->m_Flags & EF_NODRAW);
	for (int32 i=1; i<m_Chunks.Count(); i++)     
	{
		CRenderChunk *pChunkCur = &m_Chunks[i];
		SShaderItem& SH1 = pMaterial->GetShaderItem(pChunkCur->m_nMatID);
		SRenderShaderResources *pR1 = (SRenderShaderResources *)SH1.m_pShaderResources;
		CShader *pSH1 = (CShader *)SH1.m_pShader;
		bool bATCur = pR1->GetAlphaRef() > 0.0f;
		uint32 iNoShadowCur = (pR1->m_ResFlags & MTL_FLAG_NOSHADOW) || (pSH1->m_Flags & EF_NODRAW);
		if ((ch.nFirstIndexId+nInds!=pChunkCur->nFirstIndexId || ch.nFirstVertId+nVerts!=pChunkCur->nFirstVertId || bAT != bATCur || iNoShadow != iNoShadowCur ) )
		{
			ch.m_nMatID = pChunk->m_nMatID;
			ch.m_nMatFlags = pChunk->m_nMatFlags;
			ch.nNumIndices = nInds;
			ch.nNumVerts = nVerts;
			CREMeshImpl *pRE = new CREMeshImpl;
			ch.pRE = pRE;
			pRE->m_pRenderMesh = this;
			pRE->m_nFirstIndexId = ch.nFirstIndexId;
			pRE->m_nFirstVertId = ch.nFirstVertId;
			pRE->m_nNumIndices = ch.nNumIndices;
			pRE->m_nNumVerts = ch.nNumVerts;
			m_ChunksMerged.Add(ch);
			pRE->m_pChunk = &m_ChunksMerged[m_ChunksMerged.Count()-1];
			pChunk = pChunkCur;
			bAT = bATCur;
			iNoShadow = iNoShadowCur;
			ch.nFirstIndexId = pChunk->nFirstIndexId;
			ch.nFirstVertId = pChunk->nFirstVertId;
			nInds = pChunk->nNumIndices;
			nVerts = pChunk->nNumVerts;
		}
		else
		{
			nInds += pChunkCur->nNumIndices;
			nVerts += pChunkCur->nNumVerts;
		}
	}
	ch.m_nMatID = pChunk->m_nMatID;
	ch.m_nMatFlags = pChunk->m_nMatFlags;
	ch.nNumIndices = nInds;
	ch.nNumVerts = nVerts;
	CREMeshImpl *pRE = new CREMeshImpl;
	ch.pRE = pRE;
	pRE->m_pRenderMesh = this;
	pRE->m_nFirstIndexId = ch.nFirstIndexId;
	pRE->m_nFirstVertId = ch.nFirstVertId;
	pRE->m_nNumIndices = ch.nNumIndices;
	pRE->m_nNumVerts = ch.nNumVerts;
	m_ChunksMerged.Add(ch);
	pRE->m_pChunk = &m_ChunksMerged[m_ChunksMerged.Count()-1];
}

void CRenderMesh::Render(CRenderObject *pObj, const SRenderObjectModifier * pROII)
{
	FUNCTION_PROFILER_FAST(GetISystem(), PROFILE_RENDERER, g_bProfilerEnabled);

	IF(!CanRender(),0)
		return;

	CRenderer *  __restrict rd = gRenDev;
	bool bSkinned = (GetChunksSkinned() && (pObj->m_ObjFlags & (FOB_CHARACTER)));

	uint64 nMeshSubSetMask = 0;
#ifndef _RELEASE
	const char *szExcl = CRenderer::CV_r_excludemesh->GetString();
	if (szExcl[0] && m_sSource)
	{
		char szMesh[1024];
		strncpy(szMesh, this->m_sSource, 1024);
		strlwr(szMesh);
		if (szExcl[0] == '!')
		{
			if (!strstr(&szExcl[1], m_sSource))
				return;
		}
		else
			if (strstr(szExcl, m_sSource))
				return;
	}
#endif

	IMaterial * __restrict pMaterial = pObj->m_pCurrMaterial;

	if (rd->m_pDefaultMaterial && pMaterial)
		pMaterial = rd->m_pDefaultMaterial;

	assert(pMaterial);

	if (!pMaterial)
		return;

	m_nLastRenderFrameID = GetCurrentRenderFrameID();
	
	//////////////////////////////////////////////////////////////////////////
	if (!m_meshSubSetIndices.empty() && std::abs((int)m_nLastRenderFrameID - (int)m_nLastSubsetGCRenderFrameID) > DELETE_SUBSET_MESHES_AFTER_NOTUSED_FRAMES )
	{
		// Cleanup unused index render meshes periodically.
		GarbageCollectSubsetRenderMeshes();
	}
	//////////////////////////////////////////////////////////////////////////
	bool bRenderBreakableWithMultipleDrawCalls = false;
	if(pObj->m_ObjFlags&FOB_MESH_SUBSET_INDICES && m_nVerts>=3)
	{
		if(SRenderObjData *pOD = gRenDev->EF_GetObjData(pObj, false))
		{
			if(pOD->m_nSubObjHideMask)
			{
					IRenderMesh *pRM = GetRenderMeshForSubsetMask(pOD, pOD->m_nSubObjHideMask, pMaterial);
					// if pRM is null, it means that this subset rendermesh is not computed yet, thus we render it with multiple draw calls
			if (pRM)
					{
				pRM->Render(pObj, pROII);
						return;								
					}				
					// compute the needed mask				
					const uint32 ni = m_ChunksSubObjects.Count();
					nMeshSubSetMask = pOD->m_nSubObjHideMask & (((uint64)1<<ni)-1);
					bRenderBreakableWithMultipleDrawCalls = true;
			}
		}
	}

	// duplicate render object
	// TODO: handle RenderObjectModifier by renderer natively 
	//assert (!pObj->GetInstanceInfo(0));
	if (pROII) 
		pObj = gRenDev->EF_DuplicateRO(pObj, pROII);

	int nList = pROII && pROII->nRenderList ? pROII->nRenderList : EFSLIST_GENERAL;

	if(pObj->m_ObjFlags & FOB_RENDER_AFTER_POSTPROCESSING)
	{
		// Check for mesh conditions regarding post processing
		if (  CRenderer::CV_r_PostProcess && CRenderer::CV_r_PostProcessHUD3D )		
			AddHUDRenderElement(pObj, pMaterial);		

		nList = EFSLIST_AFTER_POSTPROCESS;

		return;
	}
	
	const int nAW = (pObj->m_ObjFlags & FOB_AFTER_WATER)||(pObj->m_ObjFlags & FOB_NEAREST) ? 1 : 0; 

	bool bSG = (rd->m_RP.m_TI[rd->m_RP.m_nFillThreadID].m_PersFlags & RBPF_SHADOWGEN) != 0;

	//////////////
	
	if (gRenDev->s_AllowMotionBlur >= 2 && !bSG)
		CMotionBlur::SetupObject(pObj);

	bool bAllowMergedChunks = bSG && m_Chunks.Count()>1 && CRenderer::CV_r_MergeRenderChunks && !bSkinned;

	if (!bAllowMergedChunks || bRenderBreakableWithMultipleDrawCalls)
	{		
		PodArray<CRenderChunk>* pChunks = bSkinned ? m_pChunksSkinned : &m_Chunks;
		// for rendering with multiple drawcalls, use ChunksSubObjects
		if(bRenderBreakableWithMultipleDrawCalls)
		{
			pChunks = &m_ChunksSubObjects;
		}
		const uint32 ni = (uint32)pChunks->Count();

		for (uint32 i=0; i<ni; i++)     
		{
			CRenderChunk * pChunk = pChunks->Get(i);

				// don't render this chunk if the hide mask for it is set
				if( bRenderBreakableWithMultipleDrawCalls && (nMeshSubSetMask & ((uint64)1<< pChunk->nSubObjectIndex)) )			
					continue;

			CRendElementBase *  __restrict pREMesh = pChunk->pRE;

			SShaderItem& ShaderItem = pMaterial->GetShaderItem(pChunk->m_nMatID);

			SRenderShaderResources *pR = (SRenderShaderResources *)ShaderItem.m_pShaderResources;
			CShader *  __restrict pS = (CShader *)ShaderItem.m_pShader;
			if (pREMesh && pS && pR)
			{
				if (pS->m_Flags2 & EF2_NODRAW)
					continue;

				if (bSG && (pR->m_ResFlags & MTL_FLAG_NOSHADOW))
					continue;

				if(rd->CV_r_VegetationAlphaTestOnly==1 && pS->GetShaderType() == eST_Vegetation)
					if(!pR->GetAlphaRef())
						continue;

				PrefetchLine(pREMesh, 0);
				PrefetchLine(pObj, 0);

				rd->EF_AddEf_NotVirtual(pREMesh, ShaderItem, pObj, nList, nAW);  
			}
		}
	} 
	else
	{	
		uint32 ni = (uint32)m_ChunksMerged.Count();
		if (!ni)
		{
			CreateMergedChunks(pMaterial);
			ni = m_ChunksMerged.Count();
		}
		for (uint32 i=0; i<ni; i++)     
		{
			CRenderChunk *  __restrict pChunk = &m_ChunksMerged[i];
			CRendElementBase *  __restrict pREMesh = pChunk->pRE;

			SShaderItem& ShaderItem = pMaterial->GetShaderItem(pChunk->m_nMatID);

			SRenderShaderResources * __restrict pR = (SRenderShaderResources *)ShaderItem.m_pShaderResources;
			CShader *  __restrict pS = (CShader *)ShaderItem.m_pShader;
			if (pREMesh && pS && pR)
			{
				if (pR->m_ResFlags & MTL_FLAG_NOSHADOW)
					continue;
				if (pS->m_Flags2 & EF2_NODRAW)
					continue;

				PrefetchLine(pREMesh, 0);
				PrefetchLine(pObj, 0);

				rd->EF_AddEf_NotVirtual(pREMesh, ShaderItem, pObj, nList, nAW);  
			}
		} 
	}
}

void CRenderMesh::SetREUserData(float *pfCustomData, float fFogScale, float fAlpha)
{
	for (int i=0; i<m_Chunks.Count(); i++)
	{
		if(m_Chunks[i].pRE)
		{
			m_Chunks[i].pRE->m_CustomData = pfCustomData;
		}
	}
}

void CRenderMesh::AddRenderElements(IMaterial *pIMatInfo, CRenderObject *pObj, int nList, int nAW)
{
	assert(!(pObj->m_ObjFlags & FOB_BENDED));
	//assert (!pObj->GetInstanceInfo(0));

	if(gRenDev->m_pDefaultMaterial && gRenDev->m_pTerrainDefaultMaterial)
	{
		if(nList == EFSLIST_TERRAINLAYER && pObj->GetMatrix().GetTranslation().GetLength()>1)
		{
			if (gRenDev->m_pTerrainDefaultMaterial && pIMatInfo)
				pIMatInfo = gRenDev->m_pTerrainDefaultMaterial;
		}
		else
		{
			if (gRenDev->m_pDefaultMaterial && pIMatInfo)
				pIMatInfo = gRenDev->m_pDefaultMaterial;
		}
	}

	//assert(pIMatInfo);
	if (!_GetVertexContainer()->m_nVerts || !m_Chunks.Count() || !pIMatInfo)
		return;

	for (int i=0; i<m_Chunks.Count(); i++)
	{
		CRenderChunk * pChunk = &m_Chunks[i];
		CREMeshImpl* pOrigRE = (CREMeshImpl*) pChunk->pRE;

		// get material

		SShaderItem& shaderItem = pIMatInfo->GetShaderItem(pChunk->m_nMatID);

		//    if (nTechniqueID > 0)
		//    shaderItem.m_nTechnique = shaderItem.m_pShader->GetTechniqueID(shaderItem.m_nTechnique, nTechniqueID);

		if (shaderItem.m_pShader && pOrigRE)// && pMat->nNumIndices)
		{
			TArray<CRendElementBase *> *pREs = shaderItem.m_pShader->GetREs(shaderItem.m_nTechnique);

			assert(pOrigRE->m_pChunk->nFirstIndexId<60000);

			if (!pREs || !pREs->Num())
			gRenDev->EF_AddEf_NotVirtual(pOrigRE, shaderItem, pObj, nList, nAW);
			else
			gRenDev->EF_AddEf_NotVirtual(pREs->Get(0), shaderItem, pObj, nList, nAW);
		}
	} //i
}

void CRenderMesh::AddRE(IMaterial * pMaterial, CRenderObject *obj, IShader *ef, int nList, int nAW)
{
	if (!m_nVerts || !m_Chunks.Count())
		return;

	assert(!(obj->m_ObjFlags & FOB_BENDED));

	for(int i=0; i<m_Chunks.Count(); i++)
	{
		if (!m_Chunks[i].pRE)
			continue;

		SShaderItem& SH = pMaterial->GetShaderItem();
		if (ef)
			SH.m_pShader = ef;
		if (SH.m_pShader)
		{
			assert(m_Chunks[i].pRE->m_pChunk->nFirstIndexId<60000);

			TArray<CRendElementBase *> *pRE = SH.m_pShader->GetREs(SH.m_nTechnique);
			if (!pRE || !pRE->Num())
				gRenDev->EF_AddEf_NotVirtual(m_Chunks[i].pRE, SH, obj, nList, nAW);
			else
				gRenDev->EF_AddEf_NotVirtual(SH.m_pShader->GetREs(SH.m_nTechnique)->Get(0), SH, obj, nList, nAW);
		}
	}
}

size_t CRenderMesh::GetMemoryUsage( ICrySizer* pSizer, EMemoryUsageArgument nType ) const
{
	size_t nSize = 0;
	switch (nType)
	{
	case MEM_USAGE_COMBINED:
		nSize = Size(SIZE_ONLY_SYSTEM) + Size(SIZE_VB|SIZE_IB);
		break;
	case MEM_USAGE_ONLY_SYSTEM:
		nSize = Size(SIZE_ONLY_SYSTEM);
		break;
	case MEM_USAGE_ONLY_VIDEO:
		nSize = Size(SIZE_VB|SIZE_IB);
		return nSize;
		break;
	case MEM_USAGE_ONLY_STREAMS:
		nSize = Size(SIZE_ONLY_SYSTEM) + Size(SIZE_VB|SIZE_IB);

		if (pSizer)
		{
			SIZER_COMPONENT_NAME(pSizer, "STREAM MESH");
			pSizer->AddObject((void *)this, nSize);
		}

		// Not add overhead allocations.
		return nSize;
		break;
	}

	{
		nSize += sizeof(*this);
		for (int i=0; i<(int)m_Chunks.capacity(); i++)
		{
			if (i < m_Chunks.Count())
				nSize += m_Chunks[i].Size();
			else
				nSize += sizeof(CRenderChunk);
		}
		if (m_pChunksSkinned)
		{
			for (int i=0; i<(int)m_pChunksSkinned->capacity(); i++)
			{
				if (i < m_pChunksSkinned->Count())
					nSize += m_pChunksSkinned->Get(i)->Size();
				else
					nSize += sizeof(CRenderChunk);
			}
		}
	}

	if (pSizer)
	{
		pSizer->AddObject((void *)this, nSize);

	#ifdef RENDER_MESH_TRIANGLE_HASH_MAP_SUPPORT
		if(m_pTrisMap)
		{
			SIZER_COMPONENT_NAME(pSizer, "Hash map");
			nSize += stl::size_of_map(*m_pTrisMap);
		}
	#endif

		for (MeshSubSetIndices::const_iterator it = m_meshSubSetIndices.begin(); it != m_meshSubSetIndices.end(); ++it)
		{
			// Collect memory usage for index sub-meshes.
			it->second->GetMemoryUsage(pSizer,nType);
		}
	}

	return nSize;
}

void CRenderMesh::GetMemoryUsage( ICrySizer* pSizer ) const
{
	pSizer->AddObject( this, sizeof(*this) );
#ifdef KEEP_POSITIONS_INDICES
	if(m_eType == eRMT_KeepSystem)
	{
		SIZER_COMPONENT_NAME(pSizer, "Keep System Vertex Data");
		for (uint32 i=0; i<VSF_NUM; i++)
		{
			if (m_VBStream[i].m_pUpdateData && !gRenDev->IsPoolAllocated(m_VBStream[i].m_pUpdateData) )
				pSizer->AddObject( m_VBStream[i].m_pUpdateData, GetStreamSize(i));
		}
	}
	else
#endif
	{
		SIZER_COMPONENT_NAME(pSizer, "Vertex Data");
		for (uint32 i=0; i<VSF_NUM; i++)
		{
			if (m_VBStream[i].m_pUpdateData)
				pSizer->AddObject( m_VBStream[i].m_pUpdateData, GetStreamSize(i));
		}
	}
#ifdef FP16_MESH
	{
		SIZER_COMPONENT_NAME(pSizer, "FP16 Cache");
		if (m_pCachePos)
			pSizer->AddObject(m_pCachePos, m_nVerts * sizeof(Vec3));
	}
#endif
#ifdef KEEP_POSITIONS_INDICES
	{
		SIZER_COMPONENT_NAME(pSizer, "System Vertex Copy");
		if (m_pSysPosData)
			pSizer->AddObject(m_pSysPosData, m_nVerts * sizeof(Vec3f16));
	}
#endif
	{
		SIZER_COMPONENT_NAME(pSizer, "Mesh Chunks");
		pSizer->AddObject( m_Chunks );	
	}
	{
		SIZER_COMPONENT_NAME(pSizer, "Mesh Skinned Chunks");
		pSizer->AddObject( m_pChunksSkinned );	
	}
	  
#ifdef RENDER_MESH_TRIANGLE_HASH_MAP_SUPPORT
	{
		SIZER_COMPONENT_NAME(pSizer, "Hash map");
		pSizer->AddObject( m_pTrisMap );			
	}
#endif
	for (MeshSubSetIndices::const_iterator it = m_meshSubSetIndices.begin(); it != m_meshSubSetIndices.end(); ++it)
	{
		// Collect memory usage for index sub-meshes.
		it->second->GetMemoryUsage(pSizer);
	}
}

int CRenderMesh::GetAllocatedBytes( bool bVideoMem ) const
{
	if (!bVideoMem)
		return Size(SIZE_ONLY_SYSTEM);
	else
		return Size(SIZE_VB|SIZE_IB);
}

int CRenderMesh::GetTextureMemoryUsage( const IMaterial* pMaterial, ICrySizer* pSizer, bool bStreamedIn ) const
{
	// If no input material use internal render mesh material.
	if (!pMaterial)
		return 0;

	int textureSize = 0;
	std::set<const CTexture*> used;
	for (int a=0; a<m_Chunks.Count(); a++)
	{
		const CRenderChunk * pChunk = &m_Chunks[a];

		// Override default material
		const SShaderItem& shaderItem = pMaterial->GetShaderItem(pChunk->m_nMatID);
		if (!shaderItem.m_pShaderResources)
			continue;

		const SRenderShaderResources *pRes = ( const SRenderShaderResources *)shaderItem.m_pShaderResources;

		for (int i=0; i<EFTT_MAX; i++)
		{
			if (!pRes->m_Textures[i])
				continue;

			const CTexture *pTexture = pRes->m_Textures[i]->m_Sampler.m_pTex;
			if (!pTexture)
				continue;

			if (used.find(pTexture) != used.end()) // Already used in size calculation.
				continue;
			used.insert(pTexture);

			int nTexSize = bStreamedIn ? pTexture->GetDeviceDataSize() : pTexture->GetDataSize();
			textureSize += nTexSize;

			if (pSizer)
				pSizer->AddObject(pTexture, nTexSize);
		}
	}

	return textureSize;
}

float CRenderMesh::GetAverageTrisNumPerChunk(IMaterial * pMat)
{
	float fTrisNum = 0;
	float fChunksNum = 0;

	for(int m=0; m<m_Chunks.Count(); m++)
	{
		const CRenderChunk& chunk = m_Chunks[m];
		if ((chunk.m_nMatFlags & MTL_FLAG_NODRAW) || !chunk.pRE)
			continue;

		const IMaterial *pCustMat;
		if (pMat && chunk.m_nMatID < pMat->GetSubMtlCount())
			pCustMat = pMat->GetSubMtl(chunk.m_nMatID);
		else
			pCustMat = pMat;

		if(!pCustMat)
			continue;

		const IShader * pShader = pCustMat->GetShaderItem().m_pShader;

		if(!pShader)
			continue;

		if (pShader->GetFlags2() & EF2_NODRAW)
			continue;

		fTrisNum += chunk.nNumIndices/3;
		fChunksNum++;
	}

	return fChunksNum ? (fTrisNum/fChunksNum) : 0;
}

void CRenderMesh::InitTriHash(IMaterial * pMaterial)
{
#ifdef RENDER_MESH_TRIANGLE_HASH_MAP_SUPPORT

	SAFE_DELETE(m_pTrisMap);
	m_pTrisMap = new TrisMap;

	int nPosStride=0;
	int nIndCount = m_nInds;
	const byte * pPositions = GetPosPtr(nPosStride, FSL_READ);
	const uint16 * pIndices = GetIndexPtr(FSL_READ);

	iLog->Log("CRenderMesh::InitTriHash: Tris=%d, Verts=%d, Name=%s ...", nIndCount/3, GetVerticesCount(), GetSourceName() ? GetSourceName() : "Null");

	if(pIndices && pPositions && m_Chunks.Count() && nIndCount && GetVerticesCount())
	{
		for (uint32 ii=0; ii<(uint32)m_Chunks.Count(); ii++)     
		{
			CRenderChunk *pChunk = &m_Chunks[ii];

			if (pChunk->m_nMatFlags & MTL_FLAG_NODRAW || !pChunk->pRE)
				continue;

			// skip transparent and alpha test
			const SShaderItem &shaderItem = pMaterial->GetShaderItem(pChunk->m_nMatID);
			if (!shaderItem.IsZWrite() || !shaderItem.m_pShaderResources || shaderItem.m_pShaderResources->GetAlphaRef())
				continue;

			if(shaderItem.m_pShader && shaderItem.m_pShader->GetFlags()&EF_DECAL)
				continue;

			uint32 nFirstIndex = pChunk->nFirstIndexId;
			uint32 nLastIndex = pChunk->nFirstIndexId + pChunk->nNumIndices;

			for (uint32 i = nFirstIndex; i < nLastIndex; i+=3)
			{
				int32 I0	=	pIndices[i+0];
				int32 I1	=	pIndices[i+1];
				int32 I2	=	pIndices[i+2];

				Vec3 v0 = *(Vec3*)&pPositions[nPosStride*I0];
				Vec3 v1 = *(Vec3*)&pPositions[nPosStride*I1];
				Vec3 v2 = *(Vec3*)&pPositions[nPosStride*I2];

				AABB triBox;
				triBox.min = triBox.max = v0;
				triBox.Add(v1);
				triBox.Add(v2);

				float fRayLen = CRenderer::CV_r_RenderMeshHashGridUnitSize/2;
				triBox.min -= Vec3(fRayLen,fRayLen,fRayLen);
				triBox.max += Vec3(fRayLen,fRayLen,fRayLen);

				AABB aabbCell;

				aabbCell.min = triBox.min / CRenderer::CV_r_RenderMeshHashGridUnitSize;
				aabbCell.min.x = floor(aabbCell.min.x);
				aabbCell.min.y = floor(aabbCell.min.y);
				aabbCell.min.z = floor(aabbCell.min.z);

				aabbCell.max = triBox.max / CRenderer::CV_r_RenderMeshHashGridUnitSize;
				aabbCell.max.x = ceil(aabbCell.max.x);
				aabbCell.max.y = ceil(aabbCell.max.y);
				aabbCell.max.z = ceil(aabbCell.max.z);

				for(float x=aabbCell.min.x; x<aabbCell.max.x; x++)
				{
					for(float y=aabbCell.min.y; y<aabbCell.max.y; y++)
					{
						for(float z=aabbCell.min.z; z<aabbCell.max.z; z++)
						{
							AABB cellBox;
							cellBox.min = Vec3(x,y,z)*CRenderer::CV_r_RenderMeshHashGridUnitSize;
							cellBox.max = cellBox.min + Vec3(CRenderer::CV_r_RenderMeshHashGridUnitSize,CRenderer::CV_r_RenderMeshHashGridUnitSize,CRenderer::CV_r_RenderMeshHashGridUnitSize);
							cellBox.min -= Vec3(fRayLen,fRayLen,fRayLen);
							cellBox.max += Vec3(fRayLen,fRayLen,fRayLen);
							if(!Overlap::AABB_Triangle(cellBox, v0, v1, v2))
								continue;

							int key = (int)(x*256.f*256.f + y*256.f + z);
							PodArray<std::pair<int,int> > * pTris = &(*m_pTrisMap)[key];
							std::pair<int,int> t(i,pChunk->m_nMatID);
							if(pTris->Find(t)<0)
								pTris->Add(t);
						}
					}
				}
			}
		}
	}

	iLog->LogPlus(" ok (%d)", m_pTrisMap->size());

#endif
}


const PodArray<std::pair<int,int> > * CRenderMesh::GetTrisForPosition(const Vec3 & vPos, IMaterial * pMaterial)
{
#ifdef RENDER_MESH_TRIANGLE_HASH_MAP_SUPPORT

	if(!m_pTrisMap)
	{
		AUTO_LOCK(m_getTrisForPositionLock);
		if(!m_pTrisMap)
			InitTriHash(pMaterial);
	}

	Vec3 vCellMin = vPos / CRenderer::CV_r_RenderMeshHashGridUnitSize;
	vCellMin.x = floor(vCellMin.x);
	vCellMin.y = floor(vCellMin.y);
	vCellMin.z = floor(vCellMin.z);

	int key = (int)(vCellMin.x*256.f*256.f + vCellMin.y*256.f + vCellMin.z);

	const TrisMap::iterator & iter = (*m_pTrisMap).find(key);
	if(iter != (*m_pTrisMap).end())
		return &iter->second;

#endif

	return 0;
}

void CRenderMesh::UpdateBBoxFromMesh()
{
	PROFILE_FRAME(UpdateBBoxFromMesh);

	AABB aabb; 
	aabb.Reset();

	int nVertCount = _GetVertexContainer()->GetVerticesCount();
	int nPosStride=0;
	int nIndCount = GetIndicesCount();
	const byte * pPositions = GetPosPtr(nPosStride, FSL_READ);
	const uint16 * pIndices = GetIndexPtr(FSL_READ);

	if(!pIndices || !pPositions)
	{
		//assert(!"Mesh is not ready");
		return;
	}

	for (int32 a=0; a<m_Chunks.Count(); a++)     
	{
		CRenderChunk * pChunk = &m_Chunks[a];

		if(pChunk->m_nMatFlags & MTL_FLAG_NODRAW || !pChunk->pRE)
			continue;

		uint32 nFirstIndex = pChunk->nFirstIndexId;
		uint32 nLastIndex = pChunk->nFirstIndexId + pChunk->nNumIndices;

		for (uint32 i = nFirstIndex; i < nLastIndex; i++)
		{
			int32 I0	=	pIndices[i];
			if(I0 < nVertCount)
			{
				Vec3 v0 = *(Vec3*)&pPositions[nPosStride*I0];
				aabb.Add(v0);
			}
			else
				assert(!"Index is out of range");
		}
	}

	if (!aabb.IsReset())
	{
		m_vBoxMax = aabb.max;
		m_vBoxMin = aabb.min;
	}
}

void BlendQuatDs(QuatD& qd, QuatD const aSkinQuats[], UCol indices, UCol weights)
{
	// Get 8-bit weights as floats
	f32 w0 = weights.bcolor[0];
	f32 w1 = weights.bcolor[1];
	f32 w2 = weights.bcolor[2];
	f32 w3 = weights.bcolor[3];

	QuatD const& q0 = aSkinQuats[indices.bcolor[0]];
	QuatD const& q1 = aSkinQuats[indices.bcolor[1]];
	QuatD const& q2 = aSkinQuats[indices.bcolor[2]];
	QuatD const& q3 = aSkinQuats[indices.bcolor[3]];

	// Blend quats
	qd = q0*w0 + q1*w1 + q2*w2 + q3*w3;

	qd.Normalize();
}

struct PosNormData
{
	strided_pointer<Vec3> aPos; 
	strided_pointer<SQTangents> aQTan;
	strided_pointer<SPipTangents> aTan2;
	strided_pointer<SVF_P3S_N4B_C4B_T2S> aVert;

	void GetPosNorm(PosNorm& ran, int nV)
	{
		// Position
		ran.vPos = aPos[nV];

		// Normal
		if (aQTan.data)
			ran.vNorm = GetNormal(aQTan[nV]);
		else if (aTan2.data)
			ran.vNorm = GetNormal(aTan2[nV]);
		else if (aVert.data)
			ran.vNorm = GetNormal(aVert[nV].normal);
	}
};

struct SkinnedPosNormData: PosNormData, IRenderMesh::SSkinningData
{
#if !defined(PS3) && !defined(XENON)
	strided_pointer<SVF_P3F_P3F_I4B> aMorphing;
#endif
	strided_pointer<SVF_W4B_I4B> aSkinning;

	void GetPosNorm(PosNorm& ran, int nV)
	{
		PosNormData::GetPosNorm(ran, nV);

#if !defined(PS3) && !defined(XENON)
		if (aShapeDeform && aMorphing)
		{
			SVF_P3F_P3F_I4B const& morph = aMorphing[nV];
			uint8 idx = (uint8)morph.index.dcolor;
			float fDeform = aShapeDeform[idx];
			if (fDeform < 0.0f)
				ran.vPos = morph.thin*(-fDeform) + ran.vPos*(fDeform+1);
			else if (fDeform > 0.f)
				ran.vPos = ran.vPos*(1-fDeform) + morph.fat*fDeform;
		}

		/*if (!g_arrExtMorphStream.empty())
			ran.vPos += g_arrExtMorphStream[nV];*/
#endif

		// Skinning
		if (aBoneLocs)
		{
			// Transform the vertex with skinning.
			SVF_W4B_I4B const& skinning = aSkinning[nV];
			UCol indices = aVertBones ? aVertBones[nV] : skinning.indices;

			if (aBoneRemap)
			{
				indices.bcolor[0] = aBoneRemap[indices.bcolor[0]];
				indices.bcolor[1] = aBoneRemap[indices.bcolor[1]];
				indices.bcolor[2] = aBoneRemap[indices.bcolor[2]];
				indices.bcolor[3] = aBoneRemap[indices.bcolor[3]];
			}

			QuatD qd;
			BlendQuatDs(qd, aBoneLocs, indices, skinning.weights);
			ran.vPos = qd * ran.vPos;
			ran.vNorm = qd.nq * ran.vNorm;
		}
	}
};

float CRenderMesh::GetExtent(EGeomForm eForm)
{
	if (eForm == GeomForm_Vertices)
		return (float)m_nVerts;
	CGeomExtent& ext = m_Extents.Make(eForm);
	if (!ext)
	{
		AUTO_LOCK(m_sResLock); ++m_nThreadAccessCounter; 
		uint16 *pInds = GetIndexPtr(FSL_READ);
		strided_pointer<Vec3> aPos; 
		aPos.data = (Vec3*)GetPosPtr(aPos.iStride, FSL_READ);
		if (pInds && aPos.data)
		{
			// Iterate chunks to track renderable verts
			bool* aValidVerts = new bool[m_nVerts];
			memset(aValidVerts, 0, m_nVerts);
			const PodArray<CRenderChunk>& aChunks = m_pChunksSkinned ? *m_pChunksSkinned : m_Chunks;
			for (uint c = 0; c < aChunks.size(); ++c)
			{
				const CRenderChunk& chunk = aChunks[c];
				if (chunk.pRE && !(chunk.m_nMatFlags & (MTL_FLAG_NODRAW | MTL_FLAG_REQUIRE_FORWARD_RENDERING)))
				{
	//					assert(chunk.nFirstVertId + chunk.nNumVerts <= m_nVerts);
					memset(aValidVerts + chunk.nFirstVertId, 1, chunk.nNumVerts);
				}
			}

			int nParts = TriMeshPartCount(eForm, GetIndicesCount());
			ext.ReserveParts(nParts);
			for (int i=0; i<nParts ; i++)
			{
				int aIndices[3];
				Vec3 aVec[3];
				for (int v = TriIndices(aIndices, i, eForm)-1; v >= 0; v--)
					aVec[v] = aPos[ pInds[aIndices[v]] ];
				ext.AddPart( aValidVerts[ pInds[aIndices[0]] ] ? max(TriExtent(eForm, aVec), 0.f) : 0.f );
			}
			delete[] aValidVerts;
		}
		--m_nThreadAccessCounter; 
	}
	return ext.TotalExtent();
}

void CRenderMesh::GetRandomPos(PosNorm& ran, EGeomForm eForm, SSkinningData const* pSkinning)
{
	AUTO_LOCK(m_sResLock); ++m_nThreadAccessCounter; 

	SkinnedPosNormData vdata;
	if (vdata.aPos.data = (Vec3*)GetPosPtr(vdata.aPos.iStride, FSL_READ))
	{
		if (!GetStridedArray(vdata.aQTan, VSF_QTANGENTS))
		{
			if (!GetStridedArray(vdata.aTan2, VSF_TANGENTS))
				if (_GetVertexFormat() == eVF_P3S_N4B_C4B_T2S)
					GetStridedArray(vdata.aVert, VSF_GENERAL);
		}

		if (pSkinning)
		{
			GetStridedArray(vdata.aSkinning, VSF_HWSKIN_INFO);
#if !defined(PS3) && !defined(XENON)
			GetStridedArray(vdata.aMorphing, VSF_HWSKIN_SHAPEDEFORM_INFO);
#endif
			static_cast<SSkinningData&>(vdata) = *pSkinning;
		}
		else
		{
			vdata.aBoneLocs = 0;
			vdata.aShapeDeform = 0;
		}

		if (eForm == GeomForm_Vertices || !m_Extents[eForm])
		{
			int nV = Random(m_nVerts);
			vdata.GetPosNorm(ran, nV);
		}
		else if (uint16 *pInds = GetIndexPtr(FSL_READ))
		{
			CGeomExtent const& extent = m_Extents[eForm];
			int nPart = extent.RandomPart();

			int aIndices[3];
			PosNorm aRan[3];
			for (int v = TriIndices(aIndices, nPart, eForm)-1; v >= 0; v--)
				vdata.GetPosNorm(aRan[v], pInds[aIndices[v]]);
			TriRandomPos(ran, eForm, aRan, true);
		}
	}
	else
	{
		ran.vPos.zero();
		ran.vNorm.zero();
	}
	--m_nThreadAccessCounter; 
}

#endif

int CRenderChunk::Size() const
{
	size_t nSize = sizeof(*this);
	return static_cast<int>(nSize);
}

void CRenderMesh::Size( uint32 nFlags, ICrySizer* pSizer ) const
{
	uint32 i;
	if (!nFlags)  // System size
	{
		for (i=0; i<VSF_NUM; i++)
		{
			if (m_VBStream[i].m_pUpdateData)
				pSizer->AddObject(m_VBStream[i].m_pUpdateData,GetStreamSize(i) );
		}
		if (m_IBStream.m_pUpdateData)
			pSizer->AddObject( m_IBStream.m_pUpdateData, m_nInds * sizeof(uint16) );
	#ifdef FP16_MESH
		if (m_pCachePos)
			pSizer->AddObject(m_pCachePos, m_nVerts * sizeof(Vec3) );
	#endif
	#ifdef KEEP_POSITIONS_INDICES
		if (m_pSysPosData)
			pSizer->AddObject(m_pSysPosData, m_nVerts * sizeof(Vec3f16) );
	#endif
	}
}

size_t CRenderMesh::Size(uint32 nFlags) const
{
	size_t nSize = 0;
	uint32 i;
	if (nFlags==SIZE_ONLY_SYSTEM)  // System size
	{
		for (i=0; i<VSF_NUM; i++)
		{
			if (m_VBStream[i].m_pUpdateData)
				nSize += GetStreamSize(i);
		}
		if (m_IBStream.m_pUpdateData)
			nSize += m_nInds * sizeof(uint16);
#ifdef FP16_MESH
		if (m_pCachePos)
			nSize += m_nVerts * sizeof(Vec3);
#endif
#ifdef KEEP_POSITIONS_INDICES
		if (m_pSysPosData)
			nSize += m_nVerts * sizeof(Vec3f16);
#endif
	}

	if (nFlags & SIZE_VB) // VB size
	{
		for (i=0; i<VSF_NUM; i++)
		{
			if (_HasVBStream(i))
				nSize += GetStreamSize(i);
		}
	}

	if (nFlags & SIZE_IB) // IB size
	{
		if (_HasIBStream())
			nSize += m_nInds * sizeof(uint16);
	}

	return nSize;
}

void CRenderMesh::FreeDeviceBuffers(bool bRestoreSys)
{
	uint32 i;

	for (i=0; i<VSF_NUM; i++)
	{
		if (_HasVBStream(i))
		{
			if (bRestoreSys)
			{
				LockForThreadAccess();
				void *pSrc = LockVB(i, FSL_READ | FSL_VIDEO);
				void *pDst = LockVB(i, FSL_SYSTEM_CREATE);
				cryMemcpy(pDst, pSrc, GetStreamSize(i));
				UnLockForThreadAccess();
			}
			ReleaseVB(i);
		}
	}

	if (_HasIBStream())
	{
		if (bRestoreSys)
		{
			LockForThreadAccess();
			void *pSrc = LockIB(FSL_READ | FSL_VIDEO);
			void *pDst = LockIB(FSL_SYSTEM_CREATE);
			cryMemcpy(pDst, pSrc, m_nInds * sizeof(uint16));
			UnLockForThreadAccess();
		}
		ReleaseIB();
	}
}

void CRenderMesh::FreeVB(int nStream) 
{ 
	if (m_VBStream[nStream].m_pUpdateData)
	{
		FreeMeshData(m_VBStream[nStream].m_pUpdateData);
		m_VBStream[nStream].m_pUpdateData = NULL;
	}
} 

void CRenderMesh::FreeIB() 
{ 
	if (m_IBStream.m_pUpdateData)
	{
		FreeMeshData(m_IBStream.m_pUpdateData);
		m_IBStream.m_pUpdateData = NULL;
	}
} 

void CRenderMesh::FreeSystemBuffers()
{
	uint32 i;

	for (i=0; i<VSF_NUM; i++)
	{
		FreeVB(i);
	}
	FreeIB();
#ifdef FP16_MESH
	FreeMeshData(m_pCachePos);
	m_pCachePos = NULL;
#endif
#ifdef KEEP_POSITIONS_INDICES
	FreeMeshData(m_pSysPosData);
	m_pSysPosData = NULL; 
#endif
}

//////////////////////////////////////////////////////////////////////////
void CRenderMesh::DebugDraw( const struct SGeometryDebugDrawInfo &info,uint32 nVisibleChunksMask, float fExtrdueScale )
{
	IRenderAuxGeom *pRenderAuxGeom = gEnv->pRenderer->GetIRenderAuxGeom();
	LockForThreadAccess();

	const Matrix34 &mat = info.tm;

	bool bNoCull = info.bNoCull;
	bool bNoLines = info.bNoLines;
	bool bExtrude = info.bExtrude;

	SAuxGeomRenderFlags prevRenderFlags = pRenderAuxGeom->GetRenderFlags();
	SAuxGeomRenderFlags renderFlags = prevRenderFlags;
	renderFlags.SetAlphaBlendMode( e_AlphaBlended );

	if (bNoCull)
	{
		renderFlags.SetCullMode(e_CullModeNone);
	}
	pRenderAuxGeom->SetRenderFlags(renderFlags);

	ColorB lineColor = info.lineColor;
	ColorB c = info.color;

	for (int32 a=0; a<m_Chunks.Count(); a++)     
	{
		CRenderChunk *pChunk = &m_Chunks[a];

		if (pChunk->m_nMatFlags & MTL_FLAG_NODRAW || !pChunk->pRE)
			continue;

		if (!((1<<a) & nVisibleChunksMask))
			continue;

		const int nVertCount = GetVerticesCount();
		int nPosStride=0;
		int nIndCount = GetIndicesCount();
		const byte * pPositions = GetPosPtr(nPosStride, FSL_READ);
		const uint16 *pIndices = GetIndexPtr(FSL_READ);

		uint32 nFirstIndex = pChunk->nFirstIndexId;
		uint32 nLastIndex = pChunk->nFirstIndexId + pChunk->nNumIndices;
		int nIndexStep = 3;
		for (uint32 i = nFirstIndex; i < nLastIndex-2; i+=nIndexStep)
		{
			int32 I0 = pIndices[ i ];
			int32 I1 = pIndices[ i + 1 ];
			int32 I2 = pIndices[ i + 2 ];
			//if(nIndexStep==1 && (i&1))
			//{
			//  I1	=	pIndices[i+2];
			//  I2	=	pIndices[i+1];
			//}
			assert(I0<GetVerticesCount());
			assert(I1<GetVerticesCount());
			assert(I2<GetVerticesCount());

			Vec3 v0, v1, v2;
			v0 = *(Vec3*)&pPositions[nPosStride*I0];
			v1 = *(Vec3*)&pPositions[nPosStride*I1];
			v2 = *(Vec3*)&pPositions[nPosStride*I2];
			if (bExtrude)
			{
				// Extrude vertices along the face normal for some scale
				Vec3 normal = (v1 - v0).Cross(v2 - v0);
				normal.NormalizeSafe();
				v0 += normal * fExtrdueScale;
				v1 += normal * fExtrdueScale;
				v2 += normal * fExtrdueScale;
			}

			v0 = mat.TransformPoint( v0 );
			v1 = mat.TransformPoint( v1 );
			v2 = mat.TransformPoint( v2 );

			pRenderAuxGeom->DrawTriangle( v0,c,v1,c,v2,c );

			if (!bNoLines)
			{
				pRenderAuxGeom->DrawLine( v0, lineColor, v1, lineColor );
				pRenderAuxGeom->DrawLine( v1, lineColor, v2, lineColor );
				pRenderAuxGeom->DrawLine( v2, lineColor, v0, lineColor );
			}
		}
	}
	pRenderAuxGeom->SetRenderFlags(prevRenderFlags);
	UnLockForThreadAccess();
}


//===========================================================================================================

void CRenderMesh::PrintMeshLeaks()
{
#if !defined(__SPU__)
	AUTO_LOCK(m_sLinkLock);
	for (SIntrusiveList *iter = CRenderMesh::m_MeshList.next; iter != &CRenderMesh::m_MeshList; iter = iter->next)
	{
		CRenderMesh* pRM = iter->entry<CRenderMesh, &CRenderMesh::m_Chain>();
		Warning("--- CRenderMesh %s leak after level unload", pRM->m_sSource);
	}
#endif
}

bool CRenderMesh::ClearStaleMemory(bool bLocked, int threadId)
{
	bool cleared = false; 
	bool bKeepSystem = false; 
# if !defined(__SPU__)
	CConditionalLock lock(m_sLinkLock, !bLocked);
	// Clean up the stale mesh temporary data 
	for (SIntrusiveList* iter=m_MeshDirtyList[threadId].next, *pos=iter->next; iter != &m_MeshDirtyList[threadId]; iter=pos, pos=pos->next)
	{
		CRenderMesh* pRM = iter->entry<CRenderMesh, &CRenderMesh::m_Dirty>(threadId);
		if (!pRM->m_sResLock.TryLock())
			continue; 
		// If the mesh data is still being read, skip it. The stale data will be picked up at a later point
		if (pRM->m_nThreadAccessCounter)
		{
#     if !defined(_RELEASE) && defined(RM_CATCH_EXCESSIVE_LOCKS)
			if (gEnv->pTimer->GetAsyncTime().GetSeconds()-pRM->m_lockTime > 32.f) 
			{
				CryError("data lock for mesh '%s:%s' held longer than 32 seconds", (pRM->m_sType?pRM->m_sType:"unknown"), (pRM->m_sSource?pRM->m_sSource:"unknown"));
				if (CRenderer::CV_r_BreakOnError)
					__debugbreak();
			}
#     endif 
			goto dirty_done;
		}

#if defined(DIRECT3D9) && !defined(XENON)
		bKeepSystem = pRM->m_eType == eRMT_KeepSystem || pRM->m_nFlags & FRM_READYTOUPLOAD;
#else
		bKeepSystem = pRM->m_eType == eRMT_KeepSystem;
#endif

		#ifdef FP16_MESH
		if (!bKeepSystem && pRM->m_pCachePos)
		{
			FreeMeshData(pRM->m_pCachePos);
			pRM->m_pCachePos = NULL; 
			cleared = true;
		}
		#endif
		// In DX11 we cannot lock device buffers, so we have to keep system copy
		// On PS3 we can clear them 
		#if !defined(DIRECT3D10) || defined(PS3)
		if (!bKeepSystem) 
		{
			for (int i=0; i<VSF_NUM; i++)
				pRM->FreeVB(i);
			pRM->FreeIB();
			cleared = true; 
		}
		#endif

		// ToDo: only remove this mesh from the dirty list if no stream contains dirty data anymore
		pRM->m_Dirty[threadId].erase();
		dirty_done:
		pRM->m_sResLock.Unlock();
	}
	# endif
	return cleared;
}

void CRenderMesh::UpdateModifiedMeshes(bool bLocked, int threadId)
{
# if !defined(__SPU__)
	CConditionalLock lock(m_sLinkLock, !bLocked);
	// Update device buffers on modified meshes 
	for (SIntrusiveList* iter=m_MeshModifiedList[threadId].next, *pos=iter->next; iter != &m_MeshModifiedList[threadId]; iter=pos, pos=pos->next)
	{
		CRenderMesh* pRM = iter->entry<CRenderMesh, &CRenderMesh::m_Modified>(threadId);
		if (!pRM->m_sResLock.TryLock())
			continue; 
		// If the mesh is still being updated, skip it - it'll be blockingly updated on a drawcall if required
		if (pRM->m_nThreadAccessCounter)
		{
		#     if !defined(_RELEASE) && defined(RM_CATCH_EXCESSIVE_LOCKS)
			if (gEnv->pTimer->GetAsyncTime().GetSeconds()-pRM->m_lockTime > 32.f) 
			{
				CryError("data lock for mesh '%s:%s' held longer than 32 seconds", (pRM->m_sType?pRM->m_sType:"unknown"), (pRM->m_sSource?pRM->m_sSource:"unknown"));
				if (CRenderer::CV_r_BreakOnError)
					__debugbreak();
			}
		#     endif 
			goto modified_done;
		}

		// Do not block on async updates to the buffers, they will block on the drawcall 
		if (pRM->SyncAsyncUpdate(gRenDev->m_RP.m_nProcessThreadID, false) == true)
		{
			// ToDo : 
			// - mark the mesh to not update itself if depending on how the async update was scheduled
			// - return true if no streams need further processing
			pRM->RT_CheckUpdate(pRM, pRM->_GetVertexFormat(), VSM_MASK, true);

			pRM->m_Modified[threadId].erase(); 
		}
		modified_done:
		pRM->m_sResLock.Unlock();
	}
	# endif 
}

// Mesh garbage collector
void CRenderMesh::UpdateModified()
{
	#if !defined(__SPU__)
	ASSERT_IS_RENDER_THREAD(gRenDev->m_pRT)
	const int threadId = gRenDev->m_RP.m_nProcessThreadID; 

	// Call the update and clear functions with bLocked == true even if the lock
	// was previously released in the above scope. The resasoning behind this is
	// that only the renderthread can access the below lists as they are double
	// buffered. Note: As the Lock/Unlock functions can come from the mainthread
	// and from any other thread, they still have guarded against contention!

	UpdateModifiedMeshes(true, threadId);
	# endif
}


// Mesh garbage collector
void CRenderMesh::Tick()
{
	#if !defined(__SPU__)
	ASSERT_IS_RENDER_THREAD(gRenDev->m_pRT)
	bool bKeepSystem = false;
	const int threadId = gRenDev->m_RP.m_nProcessThreadID; 
	int nFrame = gRenDev->m_RP.m_TI[gRenDev->m_RP.m_nProcessThreadID].m_nFrameUpdateID;

	// Remove deleted meshes from the list completely 
	{
		AUTO_LOCK(m_sLinkLock); 
		SIntrusiveList* garbage = &CRenderMesh::m_MeshGarbageList[nFrame & (MAX_RELEASED_MESH_FRAMES-1)];
		while (garbage != garbage->prev)
		{
			CRenderMesh* pRM = garbage->next->entry<CRenderMesh, &CRenderMesh::m_Chain>();
			SAFE_DELETE(pRM);
		}
	}

	// Call the clear functions with bLocked == true even if the lock
	// was previously released in the above scope. The resasoning behind this is
	// that only the renderthread can access the below lists as they are double
	// buffered. Note: As the Lock/Unlock functions can come from the mainthread
	// and from any other thread, they still have guarded against contention!

	ClearStaleMemory(true, threadId);

	# endif
}

void CRenderMesh::Initialize()
{
	InitializePool();
}

void CRenderMesh::ShutDown()
{
# if !defined(__SPU__)
	if (CRenderer::CV_r_releaseallresourcesonexit)
	{
		AUTO_LOCK(m_sLinkLock);
		while (&CRenderMesh::m_MeshList != CRenderMesh::m_MeshList.prev)
		{
			CRenderMesh* pRM = CRenderMesh::m_MeshList.next->entry<CRenderMesh, &CRenderMesh::m_Chain>();
			if (CRenderer::CV_r_printmemoryleaks)
			{
				float fSize = pRM->Size(SIZE_ONLY_SYSTEM)/1024.0f/1024.0f;
				iLog->Log("Warning: CRenderMesh::ShutDown: RenderMesh leak %s: %0.3fMb", pRM->m_sSource, fSize);
			}
			SAFE_RELEASE_FORCE(pRM);
		}
	}
	new (&CRenderMesh::m_MeshList) SIntrusiveList();
	new (&CRenderMesh::m_MeshGarbageList) SIntrusiveList();
	new (&CRenderMesh::m_MeshDirtyList) SIntrusiveList();
	new (&CRenderMesh::m_MeshModifiedList) SIntrusiveList();
# endif
	ShutdownPool();
}

void CRenderMesh::KeepSysMesh(bool keep)
{
	if (keep)
		m_eType = eRMT_KeepSystem;
}

void CRenderMesh::UnKeepSysMesh()
{
	m_eType = eRMT_Static;
}

//////////////////////////////////////////////////////////////////////////
void CRenderMesh::SetVertexContainer( IRenderMesh *pBuf )
{
	MEMSTAT_CONTEXT(EMemStatContextTypes::MSC_RenderMeshType, 0, this->GetTypeName());
	MEMSTAT_CONTEXT(EMemStatContextTypes::MSC_RenderMesh, 0, this->GetSourceName());

	if (m_pVertexContainer)
		((CRenderMesh *)m_pVertexContainer)->m_lstVertexContainerUsers.Delete(this);

	m_pVertexContainer = (CRenderMesh *)pBuf;

	if (m_pVertexContainer && ((CRenderMesh *)m_pVertexContainer)->m_lstVertexContainerUsers.Find(this)<0)
		((CRenderMesh *)m_pVertexContainer)->m_lstVertexContainerUsers.Add(this);
}

//////////////////////////////////////////////////////////////////////////
void CRenderMesh::AssignChunk(CRenderChunk *pChunk, CREMeshImpl *pRE)
{
	pRE->m_pChunk = pChunk;
	pRE->m_pRenderMesh = this;
	pRE->m_nFirstIndexId = pChunk->nFirstIndexId;
	pRE->m_nNumIndices = pChunk->nNumIndices;
	pRE->m_nFirstVertId = pChunk->nFirstVertId;
	pRE->m_nNumVerts = pChunk->nNumVerts;
}

//////////////////////////////////////////////////////////////////////////
void CRenderMesh::InitRenderChunk( CRenderChunk &rChunk )
{
	assert( rChunk.nNumIndices > 0 );
	assert( rChunk.nNumVerts > 0 );

	if (!rChunk.pRE)
	{
		rChunk.pRE = (CREMeshImpl*) gRenDev->EF_CreateRE(eDATA_Mesh);
		rChunk.pRE->m_CustomTexBind[0] = m_nClientTextureBindID;
	}

	// update chunk RE
	if (rChunk.pRE)
	{
		AssignChunk(&rChunk, (CREMeshImpl*) rChunk.pRE);
	}
	assert(rChunk.nFirstIndexId + rChunk.nNumIndices <= m_nInds);
}

//////////////////////////////////////////////////////////////////////////
void CRenderMesh::SetRenderChunks( CRenderChunk *pInputChunksArray,int nCount,bool bSubObjectChunks )
{
	CRenderChunkArray *pChunksArray = &m_Chunks;
	if (bSubObjectChunks)
	{
		pChunksArray = &m_ChunksSubObjects;
	}

	ReleaseRenderChunks(pChunksArray);

	pChunksArray->resize(nCount);
	for (int i = 0; i < nCount; i++)
	{
		CRenderChunk &rChunk = (*pChunksArray)[i];
		rChunk = pInputChunksArray[i];
		InitRenderChunk( rChunk );
	}
}

//////////////////////////////////////////////////////////////////////////
void CRenderMesh::GarbageCollectSubsetRenderMeshes()
{
	uint32 nFrameID = GetCurrentRenderFrameID();
	m_nLastSubsetGCRenderFrameID = nFrameID;
	for (MeshSubSetIndices::iterator it = m_meshSubSetIndices.begin(); it != m_meshSubSetIndices.end(); )
	{
		IRenderMesh *pRM = it->second;
		if (std::abs((int)nFrameID - (int)((CRenderMesh*)pRM)->m_nLastRenderFrameID) > DELETE_SUBSET_MESHES_AFTER_NOTUSED_FRAMES)
		{
			// this mesh not relevant anymore.
			it = m_meshSubSetIndices.erase(it);
		}
		else
		{
			++it;
		}
	}
}

// stop and delete all sub set indices tasks( needs to be called from main thread )
void CRenderMesh::CleanupMeshSubSetIndicesTasks()
{	
	for( MeshSubSetIndicesTasks::iterator it = m_meshSubSetIndicesTasks.begin(),end =  m_meshSubSetIndicesTasks.end() ; it != end ;  ++it )
	{		
		CMeshSubSetIndicesTask *pTask = it->m_pTask;
		while( !pTask->HasFinished() ) Sleep(1);
		IRenderMesh* indexRM = pTask->GetIndexRM();
		if (indexRM)
			indexRM->Release();
		SAFE_DELETE(pTask);
	}
	m_meshSubSetIndicesTasks.clear();
}

volatile int* CRenderMesh::SetAsyncUpdateState()
{
	AUTO_LOCK(m_sResLock); 
	ASSERT_IS_MAIN_THREAD(gRenDev->m_pRT);
	int threadID = gRenDev->m_RP.m_nFillThreadID;
	if (m_asyncUpdateStateCounter[threadID] == 0) 
	{ 
		m_asyncUpdateStateCounter[threadID] = 1; 
		++m_nThreadAccessCounter; 
	}
	CryInterlockedIncrement(&m_asyncUpdateState[threadID]);
	return &m_asyncUpdateState[threadID];
}

bool CRenderMesh::SyncAsyncUpdate(int threadID, bool block)
{
	// If this mesh is being asynchronously prepared, wait for the job to finish prior to uploading 
	// the vertices to vram. 
	AUTO_LOCK(m_sResLock); 
	if (m_asyncUpdateStateCounter[threadID]) 
	{
		FRAME_PROFILER_FAST("CRenderMesh::SyncAsyncUpdate() sync", gEnv->pSystem,PROFILE_RENDERER, true);
		while (m_asyncUpdateState[threadID]) 
		{
			if (!block) 
				return false;
			CrySleep(0); 
		}
		UnlockStream(VSF_GENERAL); 
		UnlockStream(VSF_TANGENTS); 
	#   if ENABLE_NORMALSTREAM_SUPPORT
		UnlockStream(VSF_NORMALS); 
	#   endif 
		UnlockIndexStream(); 
		m_asyncUpdateStateCounter[threadID] = 0; 
		--m_nThreadAccessCounter;
	}
	return true; 
}

void CMeshSubSetIndicesTask::OnUpdate()
{	
	CRenderChunkArray &renderChunks = ((CRenderMesh*)m_pSrcRM)->m_ChunksSubObjects;
	
	uint32 nChunkCount = renderChunks.size();
	
	IRenderMesh *pSrcRM = m_pSrcRM;
	pSrcRM->LockForThreadAccess();
	int nIndCount = pSrcRM->GetIndicesCount();
	if(uint16 *pInds = pSrcRM->GetIndexPtr(FSL_READ))
	{
		CRenderChunkArray newChunks;
		newChunks.reserve(3);

		int nMatId = -1;
		PodArray<uint16> lstIndices;
		for(uint32 c = 0; c < nChunkCount; c++)
		{
			CRenderChunk &srcChunk = renderChunks[c];
			if (0 == (m_nMeshSubSetMask & ((uint64)1<<srcChunk.nSubObjectIndex)))
			{
				uint32 nLastIndex = lstIndices.size();
				lstIndices.AddList(&pInds[srcChunk.nFirstIndexId], srcChunk.nNumIndices);
				if (newChunks.empty() || nMatId != srcChunk.m_nMatID)
				{
					// New chunk needed.
					newChunks.push_back(srcChunk);
					newChunks.back().nFirstIndexId = nLastIndex;
					newChunks.back().nNumIndices = 0;
					newChunks.back().nNumVerts = 0;
					newChunks.back().pRE = 0;
				}
				nMatId = srcChunk.m_nMatID;
				newChunks.back().nNumIndices += srcChunk.nNumIndices;				
				newChunks.back().nNumVerts = (uint16)max((int)srcChunk.nFirstVertId + (int)srcChunk.nNumVerts - (int)newChunks.back().nFirstVertId, (int)newChunks.back().nNumVerts);
			}
			pSrcRM->UnLockForThreadAccess();
		}

		IRenderMesh::SInitParamerers params;
		SVF_P3S_C4B_T2S tempVertex;
		params.pVertBuffer = &tempVertex;
		params.nVertexCount = 1;
		params.eVertexFormat = eVF_P3S_C4B_T2S;
		params.pIndices = lstIndices.GetElements();
		params.nIndexCount = lstIndices.Count();
		params.nPrimetiveType = prtTriangleList;
		params.eType = eRMT_Static;
		params.nRenderChunkCount = 1;
		params.bOnlyVideoBuffer = false;
		params.bPrecache = false;
		m_pIndexRM = gRenDev->CreateRenderMesh( ((CRenderMesh*)m_pSrcRM)->m_sType, ((CRenderMesh*)m_pSrcRM)->m_sSource,&params );
		m_pIndexRM->SetVertexContainer(pSrcRM);
		if (!newChunks.empty())
		{
			m_pIndexRM->SetRenderChunks( &newChunks.front(),newChunks.size(),false );
			m_pIndexRM->SetBBox( ((CRenderMesh*)m_pSrcRM)->m_vBoxMin, ((CRenderMesh*)m_pSrcRM)->m_vBoxMax);
		}
	}

	CMeshSubSetIndicesTask::RemoveTaskFromPool(this);	
	m_bHasFinished = true;	
}

ThreadPoolHandle CMeshSubSetIndicesTask::s_hThreadPool = - 1;

void CMeshSubSetIndicesTask::CreateThreadPool()
{
	ThreadPoolDesc threadPoolDesc;
	threadPoolDesc.sPoolName = "GetRenderMeshForSubsetMaskTaskPool";

	threadPoolDesc.CreateThread(BIT(3));
	threadPoolDesc.nThreadStackSizeKB = 16;
	IThreadTaskManager* pThreadTaskManager = gEnv->pSystem->GetIThreadTaskManager();
	assert(pThreadTaskManager);

	assert(s_hThreadPool == -1);
	s_hThreadPool = pThreadTaskManager->CreateThreadsPool(threadPoolDesc);
}

void CMeshSubSetIndicesTask::AddTaskToPool( CMeshSubSetIndicesTask *pTask )
{
	if (gEnv->IsDedicated())
	{
		return;
	}

	// Register the task with the ThreadTask manager
	IThreadTaskManager* pThreadTaskManager = gEnv->pSystem->GetIThreadTaskManager();
	assert(pThreadTaskManager);
	assert(s_hThreadPool >= 0);

	pTask->GetTaskInfo()->m_params.name = "GetRenderMeshForSubsetMaskTask";
	pTask->GetTaskInfo()->m_params.nFlags = THREAD_TASK_ASSIGN_TO_POOL;
	pTask->GetTaskInfo()->m_params.nThreadsGroupId = s_hThreadPool;
	pTask->GetTaskInfo()->m_pThread = NULL;
	pThreadTaskManager->RegisterTask(pTask, pTask->GetTaskInfo()->m_params);
}

void CMeshSubSetIndicesTask::RemoveTaskFromPool( CMeshSubSetIndicesTask *pTask )
{
	if (gEnv->IsDedicated())
	{
		return;
	}

	if (pTask->m_threadTaskInfo.m_pThread)
	{
		IThreadTaskManager* pThreadTaskManager = gEnv->pSystem->GetIThreadTaskManager();
		pThreadTaskManager->UnregisterTask(pTask);
	}
}

void CRenderMesh::LockForThreadAccess() 
{ 
	AUTO_LOCK(m_sResLock); ++m_nThreadAccessCounter; 
# if !defined(_RELEASE) && defined(RM_CATCH_EXCESSIVE_LOCKS)
	m_lockTime = (m_lockTime > 0.f) ? m_lockTime : gEnv->pTimer->GetAsyncTime().GetSeconds();
# endif 
}
void CRenderMesh::UnLockForThreadAccess() 
{ 
	AUTO_LOCK(m_sResLock); IF(m_nThreadAccessCounter > 0,1) --m_nThreadAccessCounter; 
# if !defined(_RELEASE) && defined(RM_CATCH_EXCESSIVE_LOCKS)
	m_lockTime = 0.f;
# endif 
}
void CRenderMesh::GetPoolStats(SMeshPoolStatistics* stats)
{
	memcpy(stats, &s_MeshPool.m_MeshDataPoolStats, sizeof(SMeshPoolStatistics));
}


#include UNIQUE_VIRTUAL_WRAPPER(IRenderMesh)
